blob: 2fe2494bb347e4ce8cac0fd4b4d8cadf6dca2292 [file] [log] [blame]
Kenny Root15a4d2f2010-03-11 18:20:12 -08001/*
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
Christopher Tate9bbc21a2009-06-10 20:23:25 -070017package com.android.internal.backup;
18
Christopher Tate45281862010-03-05 15:46:30 -080019import android.app.backup.BackupDataInput;
20import android.app.backup.BackupDataOutput;
21import android.app.backup.RestoreSet;
Christopher Tatecefba582013-11-14 18:10:35 -080022import android.content.ComponentName;
Christopher Tate9bbc21a2009-06-10 20:23:25 -070023import android.content.Context;
Chris Tatea8ddef32010-11-10 11:53:26 -080024import android.content.Intent;
Christopher Tate9bbc21a2009-06-10 20:23:25 -070025import android.content.pm.PackageInfo;
Christopher Tate9bbc21a2009-06-10 20:23:25 -070026import android.os.Environment;
27import android.os.ParcelFileDescriptor;
rpcraigebab0ae2012-12-04 09:37:23 -050028import android.os.SELinux;
Christopher Tate9bbc21a2009-06-10 20:23:25 -070029import android.util.Log;
30
Brian Carlstrom4140fae2011-01-24 16:17:43 -080031import com.android.org.bouncycastle.util.encoders.Base64;
Christopher Tatee9190a22009-06-17 17:52:05 -070032
Christopher Tate9bbc21a2009-06-10 20:23:25 -070033import java.io.File;
Christopher Tate9bbc21a2009-06-10 20:23:25 -070034import java.io.FileInputStream;
35import java.io.FileOutputStream;
36import java.io.IOException;
Christopher Tate9bbc21a2009-06-10 20:23:25 -070037
Christopher Tateb048c332014-02-21 12:50:21 -080038import libcore.io.ErrnoException;
39import libcore.io.Libcore;
40import libcore.io.StructStat;
41import static libcore.io.OsConstants.*;
42
Christopher Tate9bbc21a2009-06-10 20:23:25 -070043/**
44 * Backup transport for stashing stuff into a known location on disk, and
45 * later restoring from there. For testing only.
46 */
47
48public class LocalTransport extends IBackupTransport.Stub {
49 private static final String TAG = "LocalTransport";
Christopher Tate2fdd4282009-06-12 15:20:04 -070050 private static final boolean DEBUG = true;
Christopher Tate9bbc21a2009-06-10 20:23:25 -070051
Christopher Tate5cb400b2009-06-25 16:03:14 -070052 private static final String TRANSPORT_DIR_NAME
53 = "com.android.internal.backup.LocalTransport";
54
Chris Tatea8ddef32010-11-10 11:53:26 -080055 private static final String TRANSPORT_DESTINATION_STRING
56 = "Backing up to debug-only private cache";
57
Christopher Tate50c6df02010-01-29 12:48:20 -080058 // The single hardcoded restore set always has the same (nonzero!) token
59 private static final long RESTORE_TOKEN = 1;
60
Christopher Tate9bbc21a2009-06-10 20:23:25 -070061 private Context mContext;
Christopher Tate9bbc21a2009-06-10 20:23:25 -070062 private File mDataDir = new File(Environment.getDownloadCacheDirectory(), "backup");
Dan Egnorefe52642009-06-24 00:16:33 -070063 private PackageInfo[] mRestorePackages = null;
64 private int mRestorePackage = -1; // Index into mRestorePackages
Christopher Tate9bbc21a2009-06-10 20:23:25 -070065
66
67 public LocalTransport(Context context) {
68 mContext = context;
rpcraigebab0ae2012-12-04 09:37:23 -050069 mDataDir.mkdirs();
70 if (!SELinux.restorecon(mDataDir)) {
71 Log.e(TAG, "SELinux restorecon failed for " + mDataDir);
72 }
Christopher Tate9bbc21a2009-06-10 20:23:25 -070073 }
74
Christopher Tatecefba582013-11-14 18:10:35 -080075 public String name() {
76 return new ComponentName(mContext, this.getClass()).flattenToShortString();
77 }
78
Chris Tatea8ddef32010-11-10 11:53:26 -080079 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 }
Christopher Tate5cb400b2009-06-25 16:03:14 -070087
Dan Egnor01445162009-09-21 17:04:05 -070088 public String transportDirName() {
Christopher Tate5cb400b2009-06-25 16:03:14 -070089 return TRANSPORT_DIR_NAME;
90 }
91
Dan Egnor01445162009-09-21 17:04:05 -070092 public long requestBackupTime() {
Christopher Tate9bbc21a2009-06-10 20:23:25 -070093 // any time is a good time for local backup
94 return 0;
95 }
96
Dan Egnor01445162009-09-21 17:04:05 -070097 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) {
Christopher Tateb048c332014-02-21 12:50:21 -0800104 if (DEBUG) {
105 try {
106 StructStat ss = Libcore.os.fstat(data.getFileDescriptor());
107 Log.v(TAG, "performBackup() pkg=" + packageInfo.packageName
108 + " size=" + ss.st_size);
109 } catch (ErrnoException e) {
110 Log.w(TAG, "Unable to stat input file in performBackup() on "
111 + packageInfo.packageName);
112 }
113 }
Christopher Tate2fdd4282009-06-12 15:20:04 -0700114
Christopher Tate9bbc21a2009-06-10 20:23:25 -0700115 File packageDir = new File(mDataDir, packageInfo.packageName);
Christopher Tate2fdd4282009-06-12 15:20:04 -0700116 packageDir.mkdirs();
Christopher Tate9bbc21a2009-06-10 20:23:25 -0700117
Christopher Tate2fdd4282009-06-12 15:20:04 -0700118 // Each 'record' in the restore set is kept in its own file, named by
119 // the record key. Wind through the data file, extracting individual
120 // record operations and building a set of all the updates to apply
121 // in this update.
122 BackupDataInput changeSet = new BackupDataInput(data.getFileDescriptor());
123 try {
124 int bufSize = 512;
125 byte[] buf = new byte[bufSize];
126 while (changeSet.readNextHeader()) {
127 String key = changeSet.getKey();
Joe Onorato5d605dc2009-06-18 18:23:43 -0700128 String base64Key = new String(Base64.encode(key.getBytes()));
129 File entityFile = new File(packageDir, base64Key);
130
Christopher Tate2fdd4282009-06-12 15:20:04 -0700131 int dataSize = changeSet.getDataSize();
Christopher Tatee9190a22009-06-17 17:52:05 -0700132
Christopher Tatee9190a22009-06-17 17:52:05 -0700133 if (DEBUG) Log.v(TAG, "Got change set key=" + key + " size=" + dataSize
134 + " key64=" + base64Key);
Christopher Tate9bbc21a2009-06-10 20:23:25 -0700135
Joe Onorato5d605dc2009-06-18 18:23:43 -0700136 if (dataSize >= 0) {
Dianne Hackborn1afd1c92010-03-18 22:47:17 -0700137 if (entityFile.exists()) {
138 entityFile.delete();
139 }
Joe Onorato5d605dc2009-06-18 18:23:43 -0700140 FileOutputStream entity = new FileOutputStream(entityFile);
141
142 if (dataSize > bufSize) {
143 bufSize = dataSize;
144 buf = new byte[bufSize];
145 }
146 changeSet.readEntityData(buf, 0, dataSize);
Christopher Tateb048c332014-02-21 12:50:21 -0800147 if (DEBUG) {
148 try {
149 long cur = Libcore.os.lseek(data.getFileDescriptor(), 0, SEEK_CUR);
150 Log.v(TAG, " read entity data; new pos=" + cur);
151 }
152 catch (ErrnoException e) {
153 Log.w(TAG, "Unable to stat input file in performBackup() on "
154 + packageInfo.packageName);
155 }
156 }
Joe Onorato5d605dc2009-06-18 18:23:43 -0700157
158 try {
159 entity.write(buf, 0, dataSize);
160 } catch (IOException e) {
Dan Egnorefe52642009-06-24 00:16:33 -0700161 Log.e(TAG, "Unable to update key file " + entityFile.getAbsolutePath());
Christopher Tated55e18a2009-09-21 10:12:59 -0700162 return BackupConstants.TRANSPORT_ERROR;
Joe Onorato5d605dc2009-06-18 18:23:43 -0700163 } finally {
164 entity.close();
165 }
166 } else {
167 entityFile.delete();
Christopher Tate2fdd4282009-06-12 15:20:04 -0700168 }
169 }
Christopher Tated55e18a2009-09-21 10:12:59 -0700170 return BackupConstants.TRANSPORT_OK;
Christopher Tate2fdd4282009-06-12 15:20:04 -0700171 } catch (IOException e) {
172 // oops, something went wrong. abort the operation and return error.
Dan Egnorefe52642009-06-24 00:16:33 -0700173 Log.v(TAG, "Exception reading backup input:", e);
Christopher Tated55e18a2009-09-21 10:12:59 -0700174 return BackupConstants.TRANSPORT_ERROR;
Christopher Tate2fdd4282009-06-12 15:20:04 -0700175 }
Dan Egnorefe52642009-06-24 00:16:33 -0700176 }
Christopher Tate9bbc21a2009-06-10 20:23:25 -0700177
Christopher Tate25a747f2009-09-20 12:43:58 -0700178 // Deletes the contents but not the given directory
179 private void deleteContents(File dirname) {
180 File[] contents = dirname.listFiles();
181 if (contents != null) {
182 for (File f : contents) {
183 if (f.isDirectory()) {
184 // delete the directory's contents then fall through
185 // and delete the directory itself.
186 deleteContents(f);
187 }
188 f.delete();
189 }
190 }
191 }
192
Dan Egnor01445162009-09-21 17:04:05 -0700193 public int clearBackupData(PackageInfo packageInfo) {
Christopher Tateee0e78a2009-07-02 11:17:03 -0700194 if (DEBUG) Log.v(TAG, "clearBackupData() pkg=" + packageInfo.packageName);
195
196 File packageDir = new File(mDataDir, packageInfo.packageName);
Christopher Tate0abf6a02012-03-23 17:45:15 -0700197 final File[] fileset = packageDir.listFiles();
198 if (fileset != null) {
199 for (File f : fileset) {
200 f.delete();
201 }
202 packageDir.delete();
Christopher Tateee0e78a2009-07-02 11:17:03 -0700203 }
Dan Egnor01445162009-09-21 17:04:05 -0700204 return BackupConstants.TRANSPORT_OK;
Christopher Tateee0e78a2009-07-02 11:17:03 -0700205 }
206
Dan Egnor01445162009-09-21 17:04:05 -0700207 public int finishBackup() {
Dan Egnorefe52642009-06-24 00:16:33 -0700208 if (DEBUG) Log.v(TAG, "finishBackup()");
Dan Egnor01445162009-09-21 17:04:05 -0700209 return BackupConstants.TRANSPORT_OK;
Christopher Tate9bbc21a2009-06-10 20:23:25 -0700210 }
211
212 // Restore handling
213 public RestoreSet[] getAvailableRestoreSets() throws android.os.RemoteException {
214 // one hardcoded restore set
Christopher Tate50c6df02010-01-29 12:48:20 -0800215 RestoreSet set = new RestoreSet("Local disk image", "flash", RESTORE_TOKEN);
Christopher Tatef68eb502009-06-16 11:02:01 -0700216 RestoreSet[] array = { set };
217 return array;
Christopher Tate9bbc21a2009-06-10 20:23:25 -0700218 }
219
Christopher Tate50c6df02010-01-29 12:48:20 -0800220 public long getCurrentRestoreSet() {
221 // The hardcoded restore set always has the same token
222 return RESTORE_TOKEN;
223 }
224
Dan Egnor01445162009-09-21 17:04:05 -0700225 public int startRestore(long token, PackageInfo[] packages) {
Dan Egnorefe52642009-06-24 00:16:33 -0700226 if (DEBUG) Log.v(TAG, "start restore " + token);
227 mRestorePackages = packages;
228 mRestorePackage = -1;
Dan Egnor01445162009-09-21 17:04:05 -0700229 return BackupConstants.TRANSPORT_OK;
Christopher Tate9bbc21a2009-06-10 20:23:25 -0700230 }
231
Dan Egnorefe52642009-06-24 00:16:33 -0700232 public String nextRestorePackage() {
233 if (mRestorePackages == null) throw new IllegalStateException("startRestore not called");
234 while (++mRestorePackage < mRestorePackages.length) {
235 String name = mRestorePackages[mRestorePackage].packageName;
236 if (new File(mDataDir, name).isDirectory()) {
237 if (DEBUG) Log.v(TAG, " nextRestorePackage() = " + name);
238 return name;
239 }
240 }
Christopher Tate9bbc21a2009-06-10 20:23:25 -0700241
Dan Egnorefe52642009-06-24 00:16:33 -0700242 if (DEBUG) Log.v(TAG, " no more packages to restore");
243 return "";
244 }
245
Dan Egnor01445162009-09-21 17:04:05 -0700246 public int getRestoreData(ParcelFileDescriptor outFd) {
Dan Egnorefe52642009-06-24 00:16:33 -0700247 if (mRestorePackages == null) throw new IllegalStateException("startRestore not called");
248 if (mRestorePackage < 0) throw new IllegalStateException("nextRestorePackage not called");
249 File packageDir = new File(mDataDir, mRestorePackages[mRestorePackage].packageName);
Christopher Tate9bbc21a2009-06-10 20:23:25 -0700250
Christopher Tate2fdd4282009-06-12 15:20:04 -0700251 // The restore set is the concatenation of the individual record blobs,
252 // each of which is a file in the package's directory
253 File[] blobs = packageDir.listFiles();
Dan Egnor01445162009-09-21 17:04:05 -0700254 if (blobs == null) { // nextRestorePackage() ensures the dir exists, so this is an error
Dan Egnorefe52642009-06-24 00:16:33 -0700255 Log.e(TAG, "Error listing directory: " + packageDir);
Dan Egnor01445162009-09-21 17:04:05 -0700256 return BackupConstants.TRANSPORT_ERROR;
Christopher Tate2fdd4282009-06-12 15:20:04 -0700257 }
Dan Egnorefe52642009-06-24 00:16:33 -0700258
259 // We expect at least some data if the directory exists in the first place
260 if (DEBUG) Log.v(TAG, " getRestoreData() found " + blobs.length + " key files");
261 BackupDataOutput out = new BackupDataOutput(outFd.getFileDescriptor());
262 try {
263 for (File f : blobs) {
264 FileInputStream in = new FileInputStream(f);
265 try {
266 int size = (int) f.length();
267 byte[] buf = new byte[size];
268 in.read(buf);
269 String key = new String(Base64.decode(f.getName()));
270 if (DEBUG) Log.v(TAG, " ... key=" + key + " size=" + size);
271 out.writeEntityHeader(key, size);
272 out.writeEntityData(buf, size);
273 } finally {
274 in.close();
275 }
276 }
Dan Egnor01445162009-09-21 17:04:05 -0700277 return BackupConstants.TRANSPORT_OK;
Dan Egnorefe52642009-06-24 00:16:33 -0700278 } catch (IOException e) {
279 Log.e(TAG, "Unable to read backup records", e);
Dan Egnor01445162009-09-21 17:04:05 -0700280 return BackupConstants.TRANSPORT_ERROR;
Dan Egnorefe52642009-06-24 00:16:33 -0700281 }
Christopher Tate9bbc21a2009-06-10 20:23:25 -0700282 }
Christopher Tate3a31a932009-06-22 15:10:30 -0700283
Dan Egnorefe52642009-06-24 00:16:33 -0700284 public void finishRestore() {
285 if (DEBUG) Log.v(TAG, "finishRestore()");
Christopher Tate3a31a932009-06-22 15:10:30 -0700286 }
Christopher Tate9bbc21a2009-06-10 20:23:25 -0700287}