Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit b0628bfd authored by Christopher Tate's avatar Christopher Tate
Browse files

Implement shared-storage full backup/restore

Every available shared-storage volume is backed up, tagged with its
ordinal in the set of mounted shared volumes.  This is an approximation
of "internal + the external card".  This lets us restore things to the
same volume [or "equivalent" volume, in the case of a cross-model
restore] as they originated on.

Also fixed a bug in the handling of files/dirs with spaces in
their names.

Change-Id: I380019da8d0bb5b3699bd7c11eeff621a88e78c3
parent 5c54f4b3
Loading
Loading
Loading
Loading
+0 −1
Original line number Diff line number Diff line
@@ -278,7 +278,6 @@ public abstract class BackupAgent extends ContextWrapper {
                int token, IBackupManager callbackBinder) throws RemoteException {
            long ident = Binder.clearCallingIdentity();
            try {
Log.d(TAG, "doRestoreFile() => onRestoreFile()");
                BackupAgent.this.onRestoreFile(data, size, type, domain, path, mode, mtime);
            } catch (IOException e) {
                throw new RuntimeException(e);
+4 −3
Original line number Diff line number Diff line
@@ -46,7 +46,7 @@ public class FullBackup {
    public static final String SHARED_STORAGE_TOKEN = "shared";

    public static final String APPS_PREFIX = "apps/";
    public static final String SHARED_PREFIX = "shared/";
    public static final String SHARED_PREFIX = SHARED_STORAGE_TOKEN + "/";

    public static final String FULL_BACKUP_INTENT_ACTION = "fullback";
    public static final String FULL_RESTORE_INTENT_ACTION = "fullrest";
@@ -61,7 +61,8 @@ public class FullBackup {
            String linkdomain, String rootpath, String path, BackupDataOutput output);

    static public void restoreToFile(ParcelFileDescriptor data,
            long size, int type, long mode, long mtime, File outFile) throws IOException {
            long size, int type, long mode, long mtime, File outFile,
            boolean doChmod) throws IOException {
        if (type == FullBackup.TYPE_DIRECTORY) {
            // Canonically a directory has no associated content, so we don't need to read
            // anything from the pipe in this case.  Just create the directory here and
@@ -116,7 +117,7 @@ public class FullBackup {
        }

        // Now twiddle the state to match the backup, assuming all went well
        if (outFile != null) {
        if (doChmod && outFile != null) {
            try {
                Libcore.os.chmod(outFile.getPath(), (int)mode);
            } catch (ErrnoException e) {
+14 −12
Original line number Diff line number Diff line
@@ -28,8 +28,6 @@ import libcore.io.OsConstants;
import libcore.io.StructStat;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.HashSet;
import java.util.LinkedList;
@@ -84,9 +82,10 @@ public class FullBackupAgent extends BackupAgent {

    @Override
    public void onBackup(ParcelFileDescriptor oldState, BackupDataOutput data,
            ParcelFileDescriptor newState) {
            ParcelFileDescriptor newState) throws IOException {
        // Filters, the scan queue, and the set of resulting entities
        HashSet<String> filterSet = new HashSet<String>();
        String packageName = getPackageName();

        // Okay, start with the app's root tree, but exclude all of the canonical subdirs
        if (mLibDir != null) {
@@ -96,25 +95,28 @@ public class FullBackupAgent extends BackupAgent {
        filterSet.add(mDatabaseDir);
        filterSet.add(mSharedPrefsDir);
        filterSet.add(mFilesDir);
        processTree(FullBackup.ROOT_TREE_TOKEN, mMainDir, filterSet, data);
        processTree(packageName, FullBackup.ROOT_TREE_TOKEN, mMainDir, filterSet, data);

        // Now do the same for the files dir, db dir, and shared prefs dir
        filterSet.add(mMainDir);
        filterSet.remove(mFilesDir);
        processTree(FullBackup.DATA_TREE_TOKEN, mFilesDir, filterSet, data);
        processTree(packageName, FullBackup.DATA_TREE_TOKEN, mFilesDir, filterSet, data);

        filterSet.add(mFilesDir);
        filterSet.remove(mDatabaseDir);
        processTree(FullBackup.DATABASE_TREE_TOKEN, mDatabaseDir, filterSet, data);
        processTree(packageName, FullBackup.DATABASE_TREE_TOKEN, mDatabaseDir, filterSet, data);

        filterSet.add(mDatabaseDir);
        filterSet.remove(mSharedPrefsDir);
        processTree(FullBackup.SHAREDPREFS_TREE_TOKEN, mSharedPrefsDir, filterSet, data);
        processTree(packageName, FullBackup.SHAREDPREFS_TREE_TOKEN, mSharedPrefsDir, filterSet, data);
    }

    private void processTree(String domain, String rootPath,
    // Scan the dir tree (if it actually exists) and process each entry we find.  If the
    // 'excludes' parameter is non-null, it is consulted each time a new file system entity
    // is visited to see whether that entity (and its subtree, if appropriate) should be
    // omitted from the backup process.
    protected void processTree(String packageName, String domain, String rootPath,
            HashSet<String> excludes, BackupDataOutput data) {
        // Scan the dir tree (if it actually exists) and process each entry we find
        File rootFile = new File(rootPath);
        if (rootFile.exists()) {
            LinkedList<File> scanQueue = new LinkedList<File>();
@@ -125,7 +127,7 @@ public class FullBackupAgent extends BackupAgent {
                String filePath = file.getAbsolutePath();

                // prune this subtree?
                if (excludes.contains(filePath)) {
                if (excludes != null && excludes.contains(filePath)) {
                    continue;
                }

@@ -149,7 +151,7 @@ public class FullBackupAgent extends BackupAgent {
                }

                // Finally, back this file up before proceeding
                FullBackup.backupToTar(getPackageName(), domain, null, rootPath, filePath, data);
                FullBackup.backupToTar(packageName, domain, null, rootPath, filePath, data);
            }
        }
    }
@@ -218,6 +220,6 @@ public class FullBackupAgent extends BackupAgent {
        if (DEBUG) Log.i(TAG, "[" + domain + " : " + relpath + "] mapped to " + outFile.getPath());

        // Now that we've figured out where the data goes, send it on its way
        FullBackup.restoreToFile(data, size, type, mode, mtime, outFile);
        FullBackup.restoreToFile(data, size, type, mode, mtime, outFile, true);
    }
}
+2 −1
Original line number Diff line number Diff line
@@ -1517,11 +1517,12 @@ public class PackageParser {
            }
        }

        // fullBackupAgent is explicitly handled even if allowBackup is false
        name = sa.getNonConfigurationString(
                com.android.internal.R.styleable.AndroidManifestApplication_fullBackupAgent, 0);
        if (name != null) {
            ai.fullBackupAgentName = buildClassName(pkgName, name, outError);
            if (true) {
            if (false) {
                Log.v(TAG, "android:fullBackupAgent=" + ai.fullBackupAgentName
                        + " from " + pkgName + "+" + name);
            }
+2 −0
Original line number Diff line number Diff line
@@ -73,6 +73,8 @@ static struct {
static int backupToTar(JNIEnv* env, jobject clazz, jstring packageNameObj,
        jstring domainObj, jstring linkdomain,
        jstring rootpathObj, jstring pathObj, jobject dataOutputObj) {
    int ret;

    // Extract the various strings, allowing for null object pointers
    const char* packagenamechars = env->GetStringUTFChars(packageNameObj, NULL);
    const char* rootchars = env->GetStringUTFChars(rootpathObj, NULL);
Loading