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