FullBackupAgent.java revision b0628bfd5aac480a0d412ac96b8af1d97ac01c30
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
17package android.app.backup;
18
19import android.content.pm.ApplicationInfo;
20import android.content.pm.PackageManager;
21import android.os.Environment;
22import android.os.ParcelFileDescriptor;
23import android.util.Log;
24
25import libcore.io.Libcore;
26import libcore.io.ErrnoException;
27import libcore.io.OsConstants;
28import libcore.io.StructStat;
29
30import java.io.File;
31import java.io.IOException;
32import java.util.HashSet;
33import java.util.LinkedList;
34
35/**
36 * Backs up an application's entire /data/data/<package>/... file system.  This
37 * class is used by the desktop full backup mechanism and is not intended for direct
38 * use by applications.
39 *
40 * {@hide}
41 */
42
43public class FullBackupAgent extends BackupAgent {
44    // !!! TODO: turn off debugging
45    private static final String TAG = "FullBackupAgent";
46    private static final boolean DEBUG = true;
47
48    PackageManager mPm;
49
50    private String mMainDir;
51    private String mFilesDir;
52    private String mDatabaseDir;
53    private String mSharedPrefsDir;
54    private String mCacheDir;
55    private String mLibDir;
56
57    private File NULL_FILE;
58
59    @Override
60    public void onCreate() {
61        NULL_FILE = new File("/dev/null");
62
63        mPm = getPackageManager();
64        try {
65            ApplicationInfo appInfo = mPm.getApplicationInfo(getPackageName(), 0);
66            mMainDir = new File(appInfo.dataDir).getAbsolutePath();
67        } catch (PackageManager.NameNotFoundException e) {
68            Log.e(TAG, "Unable to find package " + getPackageName());
69            throw new RuntimeException(e);
70        }
71
72        mFilesDir = getFilesDir().getAbsolutePath();
73        mDatabaseDir = getDatabasePath("foo").getParentFile().getAbsolutePath();
74        mSharedPrefsDir = getSharedPrefsFile("foo").getParentFile().getAbsolutePath();
75        mCacheDir = getCacheDir().getAbsolutePath();
76
77        ApplicationInfo app = getApplicationInfo();
78        mLibDir = (app.nativeLibraryDir != null)
79                ? new File(app.nativeLibraryDir).getAbsolutePath()
80                : null;
81    }
82
83    @Override
84    public void onBackup(ParcelFileDescriptor oldState, BackupDataOutput data,
85            ParcelFileDescriptor newState) throws IOException {
86        // Filters, the scan queue, and the set of resulting entities
87        HashSet<String> filterSet = new HashSet<String>();
88        String packageName = getPackageName();
89
90        // Okay, start with the app's root tree, but exclude all of the canonical subdirs
91        if (mLibDir != null) {
92            filterSet.add(mLibDir);
93        }
94        filterSet.add(mCacheDir);
95        filterSet.add(mDatabaseDir);
96        filterSet.add(mSharedPrefsDir);
97        filterSet.add(mFilesDir);
98        processTree(packageName, FullBackup.ROOT_TREE_TOKEN, mMainDir, filterSet, data);
99
100        // Now do the same for the files dir, db dir, and shared prefs dir
101        filterSet.add(mMainDir);
102        filterSet.remove(mFilesDir);
103        processTree(packageName, FullBackup.DATA_TREE_TOKEN, mFilesDir, filterSet, data);
104
105        filterSet.add(mFilesDir);
106        filterSet.remove(mDatabaseDir);
107        processTree(packageName, FullBackup.DATABASE_TREE_TOKEN, mDatabaseDir, filterSet, data);
108
109        filterSet.add(mDatabaseDir);
110        filterSet.remove(mSharedPrefsDir);
111        processTree(packageName, FullBackup.SHAREDPREFS_TREE_TOKEN, mSharedPrefsDir, filterSet, data);
112    }
113
114    // Scan the dir tree (if it actually exists) and process each entry we find.  If the
115    // 'excludes' parameter is non-null, it is consulted each time a new file system entity
116    // is visited to see whether that entity (and its subtree, if appropriate) should be
117    // omitted from the backup process.
118    protected void processTree(String packageName, String domain, String rootPath,
119            HashSet<String> excludes, BackupDataOutput data) {
120        File rootFile = new File(rootPath);
121        if (rootFile.exists()) {
122            LinkedList<File> scanQueue = new LinkedList<File>();
123            scanQueue.add(rootFile);
124
125            while (scanQueue.size() > 0) {
126                File file = scanQueue.remove(0);
127                String filePath = file.getAbsolutePath();
128
129                // prune this subtree?
130                if (excludes != null && excludes.contains(filePath)) {
131                    continue;
132                }
133
134                // If it's a directory, enqueue its contents for scanning.
135                try {
136                    StructStat stat = Libcore.os.lstat(filePath);
137                    if (OsConstants.S_ISLNK(stat.st_mode)) {
138                        if (DEBUG) Log.i(TAG, "Symlink (skipping)!: " + file);
139                        continue;
140                    } else if (OsConstants.S_ISDIR(stat.st_mode)) {
141                        File[] contents = file.listFiles();
142                        if (contents != null) {
143                            for (File entry : contents) {
144                                scanQueue.add(0, entry);
145                            }
146                        }
147                    }
148                } catch (ErrnoException e) {
149                    if (DEBUG) Log.w(TAG, "Error scanning file " + file + " : " + e);
150                    continue;
151                }
152
153                // Finally, back this file up before proceeding
154                FullBackup.backupToTar(packageName, domain, null, rootPath, filePath, data);
155            }
156        }
157    }
158
159    @Override
160    void onSaveApk(BackupDataOutput data) {
161        ApplicationInfo app = getApplicationInfo();
162        if (DEBUG) Log.i(TAG, "APK flags: system=" + ((app.flags & ApplicationInfo.FLAG_SYSTEM) != 0)
163                + " updated=" + ((app.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0)
164                + " locked=" + ((app.flags & ApplicationInfo.FLAG_FORWARD_LOCK) != 0) );
165        if (DEBUG) Log.i(TAG, "codepath: " + getPackageCodePath());
166
167        // Forward-locked apps, system-bundled .apks, etc are filtered out before we get here
168        final String pkgName = getPackageName();
169        final String apkDir = new File(getPackageCodePath()).getParent();
170        FullBackup.backupToTar(pkgName, FullBackup.APK_TREE_TOKEN, null,
171                apkDir, getPackageCodePath(), data);
172
173        // Save associated .obb content if it exists and we did save the apk
174        // check for .obb and save those too
175        final File obbDir = Environment.getExternalStorageAppObbDirectory(pkgName);
176        if (obbDir != null) {
177            if (DEBUG) Log.i(TAG, "obb dir: " + obbDir.getAbsolutePath());
178            File[] obbFiles = obbDir.listFiles();
179            if (obbFiles != null) {
180                final String obbDirName = obbDir.getAbsolutePath();
181                for (File obb : obbFiles) {
182                    FullBackup.backupToTar(pkgName, FullBackup.OBB_TREE_TOKEN, null,
183                            obbDirName, obb.getAbsolutePath(), data);
184                }
185            }
186        }
187    }
188
189    /**
190     * Dummy -- We're never used for restore of an incremental dataset
191     */
192    @Override
193    public void onRestore(BackupDataInput data, int appVersionCode, ParcelFileDescriptor newState)
194            throws IOException {
195    }
196
197    /**
198     * Restore the described file from the given pipe.
199     */
200    @Override
201    public void onRestoreFile(ParcelFileDescriptor data, long size,
202            int type, String domain, String relpath, long mode, long mtime)
203            throws IOException {
204        String basePath = null;
205        File outFile = null;
206
207        if (DEBUG) Log.d(TAG, "onRestoreFile() size=" + size + " type=" + type
208                + " domain=" + domain + " relpath=" + relpath + " mode=" + mode
209                + " mtime=" + mtime);
210
211        // Parse out the semantic domains into the correct physical location
212        if (domain.equals(FullBackup.DATA_TREE_TOKEN)) basePath = mFilesDir;
213        else if (domain.equals(FullBackup.DATABASE_TREE_TOKEN)) basePath = mDatabaseDir;
214        else if (domain.equals(FullBackup.ROOT_TREE_TOKEN)) basePath = mMainDir;
215        else if (domain.equals(FullBackup.SHAREDPREFS_TREE_TOKEN)) basePath = mSharedPrefsDir;
216
217        // Not a supported output location?  We need to consume the data
218        // anyway, so send it to /dev/null
219        outFile = (basePath != null) ? new File(basePath, relpath) : null;
220        if (DEBUG) Log.i(TAG, "[" + domain + " : " + relpath + "] mapped to " + outFile.getPath());
221
222        // Now that we've figured out where the data goes, send it on its way
223        FullBackup.restoreToFile(data, size, type, mode, mtime, outFile, true);
224    }
225}
226