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

Commit aa870a47 authored by Michal Karpinski's avatar Michal Karpinski Committed by Android (Google) Code Review
Browse files

Merge "Add support for key-value packages to adb backup/restore."

parents b58609d7 b59a4b85
Loading
Loading
Loading
Loading
+10 −7
Original line number Diff line number Diff line
@@ -53,15 +53,15 @@ public final class Backup {

        String arg = nextArg();
        if (arg.equals("backup")) {
            doFullBackup(OsConstants.STDOUT_FILENO);
            doBackup(OsConstants.STDOUT_FILENO);
        } else if (arg.equals("restore")) {
            doFullRestore(OsConstants.STDIN_FILENO);
            doRestore(OsConstants.STDIN_FILENO);
        } else {
            Log.e(TAG, "Invalid operation '" + arg + "'");
        }
    }

    private void doFullBackup(int socketFd) {
    private void doBackup(int socketFd) {
        ArrayList<String> packages = new ArrayList<String>();
        boolean saveApks = false;
        boolean saveObbs = false;
@@ -70,6 +70,7 @@ public final class Backup {
        boolean doWidgets = false;
        boolean allIncludesSystem = true;
        boolean doCompress = true;
        boolean doKeyValue = false;

        String arg;
        while ((arg = nextArg()) != null) {
@@ -100,6 +101,8 @@ public final class Backup {
                    doCompress = true;
                } else if ("-nocompress".equals(arg)) {
                    doCompress = false;
                } else if ("-includekeyvalue".equals(arg)) {
                    doKeyValue = true;
                } else {
                    Log.w(TAG, "Unknown backup flag " + arg);
                    continue;
@@ -123,8 +126,8 @@ public final class Backup {
        try {
            fd = ParcelFileDescriptor.adoptFd(socketFd);
            String[] packArray = new String[packages.size()];
            mBackupManager.fullBackup(fd, saveApks, saveObbs, saveShared, doWidgets,
                    doEverything, allIncludesSystem, doCompress, packages.toArray(packArray));
            mBackupManager.adbBackup(fd, saveApks, saveObbs, saveShared, doWidgets, doEverything,
                    allIncludesSystem, doCompress, doKeyValue, packages.toArray(packArray));
        } catch (RemoteException e) {
            Log.e(TAG, "Unable to invoke backup manager for backup");
        } finally {
@@ -136,12 +139,12 @@ public final class Backup {
        }
    }

    private void doFullRestore(int socketFd) {
    private void doRestore(int socketFd) {
        // No arguments to restore
        ParcelFileDescriptor fd = null;
        try {
            fd = ParcelFileDescriptor.adoptFd(socketFd);
            mBackupManager.fullRestore(fd);
            mBackupManager.adbRestore(fd);
        } catch (RemoteException e) {
            Log.e(TAG, "Unable to invoke backup manager for restore");
        } finally {
+1 −0
Original line number Diff line number Diff line
@@ -56,6 +56,7 @@ public class FullBackup {

    public static final String APK_TREE_TOKEN = "a";
    public static final String OBB_TREE_TOKEN = "obb";
    public static final String KEY_VALUE_DATA_TOKEN = "k";

    public static final String ROOT_TREE_TOKEN = "r";
    public static final String FILES_TREE_TOKEN = "f";
+9 −5
Original line number Diff line number Diff line
@@ -144,9 +144,10 @@ interface IBackupManager {
    void backupNow();

    /**
     * Write a full backup of the given package to the supplied file descriptor.
     * Write a backup of the given package to the supplied file descriptor.
     * The fd may be a socket or other non-seekable destination.  If no package names
     * are supplied, then every application on the device will be backed up to the output.
     * Currently only used by the 'adb backup' command.
     *
     * <p>This method is <i>synchronous</i> -- it does not return until the backup has
     * completed.
@@ -167,12 +168,14 @@ interface IBackupManager {
     *     as including packages pre-installed as part of the system. If {@code false},
     *     then setting {@code allApps} to {@code true} will mean only that all 3rd-party
     *     applications will be included in the dataset.
     * @param doKeyValue If {@code true}, also packages supporting key-value backup will be backed
     *     up. If {@code false}, key-value packages will be skipped.
     * @param packageNames The package names of the apps whose data (and optionally .apk files)
     *     are to be backed up.  The <code>allApps</code> parameter supersedes this.
     */
    void fullBackup(in ParcelFileDescriptor fd, boolean includeApks, boolean includeObbs,
    void adbBackup(in ParcelFileDescriptor fd, boolean includeApks, boolean includeObbs,
            boolean includeShared, boolean doWidgets, boolean allApps, boolean allIncludesSystem,
            boolean doCompress, in String[] packageNames);
            boolean doCompress, boolean doKeyValue, in String[] packageNames);

    /**
     * Perform a full-dataset backup of the given applications via the currently active
@@ -184,11 +187,12 @@ interface IBackupManager {

    /**
     * Restore device content from the data stream passed through the given socket.  The
     * data stream must be in the format emitted by fullBackup().
     * data stream must be in the format emitted by adbBackup().
     * Currently only used by the 'adb restore' command.
     *
     * <p>Callers must hold the android.permission.BACKUP permission to use this method.
     */
    void fullRestore(in ParcelFileDescriptor fd);
    void adbRestore(in ParcelFileDescriptor fd);

    /**
     * Confirm that the requested full backup/restore operation can proceed.  The system will
+197 −136

File changed.

Preview size limit exceeded, changes collapsed.

+281 −0
Original line number Diff line number Diff line
package com.android.server.backup;

import static android.os.ParcelFileDescriptor.MODE_CREATE;
import static android.os.ParcelFileDescriptor.MODE_READ_ONLY;
import static android.os.ParcelFileDescriptor.MODE_READ_WRITE;
import static android.os.ParcelFileDescriptor.MODE_TRUNCATE;
import static com.android.server.backup.BackupManagerService.OP_TYPE_BACKUP_WAIT;
import static com.android.server.backup.BackupManagerService.TIMEOUT_BACKUP_INTERVAL;

import android.app.ApplicationThreadConstants;
import android.app.IBackupAgent;
import android.app.backup.FullBackup;
import android.app.backup.FullBackupDataOutput;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
import android.os.SELinux;
import android.util.Slog;

import libcore.io.IoUtils;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;

/**
 * Used by BackupManagerService to perform adb backup for key-value packages. At the moment this
 * class resembles what is done in the standard key-value code paths in BackupManagerService, and
 * should be unified later.
 *
 * TODO: We should create unified backup/restore engines that can be used for both transport and
 * adb backup/restore, and for fullbackup and key-value backup.
 */
class KeyValueAdbBackupEngine {
    private static final String TAG = "KeyValueAdbBackupEngine";
    private static final boolean DEBUG = false;

    private static final String BACKUP_KEY_VALUE_DIRECTORY_NAME = "key_value_dir";
    private static final String BACKUP_KEY_VALUE_BLANK_STATE_FILENAME = "blank_state";
    private static final String BACKUP_KEY_VALUE_BACKUP_DATA_FILENAME_SUFFIX = ".data";
    private static final String BACKUP_KEY_VALUE_NEW_STATE_FILENAME_SUFFIX = ".new";

    private BackupManagerService mBackupManagerService;
    private final PackageManager mPackageManager;
    private final OutputStream mOutput;
    private final PackageInfo mCurrentPackage;
    private final File mDataDir;
    private final File mStateDir;
    private final File mBlankStateName;
    private final File mBackupDataName;
    private final File mNewStateName;
    private final File mManifestFile;
    private ParcelFileDescriptor mSavedState;
    private ParcelFileDescriptor mBackupData;
    private ParcelFileDescriptor mNewState;

    KeyValueAdbBackupEngine(OutputStream output, PackageInfo packageInfo,
            BackupManagerService backupManagerService, PackageManager packageManager,
            File baseStateDir, File dataDir) {
        mOutput = output;
        mCurrentPackage = packageInfo;
        mBackupManagerService = backupManagerService;
        mPackageManager = packageManager;

        mDataDir = dataDir;
        mStateDir = new File(baseStateDir, BACKUP_KEY_VALUE_DIRECTORY_NAME);
        mStateDir.mkdirs();

        String pkg = mCurrentPackage.packageName;

        mBlankStateName = new File(mStateDir, BACKUP_KEY_VALUE_BLANK_STATE_FILENAME);
        mBackupDataName = new File(mDataDir,
                pkg + BACKUP_KEY_VALUE_BACKUP_DATA_FILENAME_SUFFIX);
        mNewStateName = new File(mStateDir,
                pkg + BACKUP_KEY_VALUE_NEW_STATE_FILENAME_SUFFIX);

        mManifestFile = new File(mDataDir, BackupManagerService.BACKUP_MANIFEST_FILENAME);
    }

    void backupOnePackage() throws IOException {
        ApplicationInfo targetApp = mCurrentPackage.applicationInfo;

        try {
            prepareBackupFiles(mCurrentPackage.packageName);

            IBackupAgent agent = bindToAgent(targetApp);

            if (agent == null) {
                // We failed binding to the agent, so ignore this package
                Slog.e(TAG, "Failed binding to BackupAgent for package "
                        + mCurrentPackage.packageName);
                return;
            }

            // We are bound to agent, initiate backup.
            if (!invokeAgentForAdbBackup(mCurrentPackage.packageName, agent)) {
                // Backup failed, skip package.
                Slog.e(TAG, "Backup Failed for package " + mCurrentPackage.packageName);
                return;
            }

            // Backup finished successfully. Copy the backup data to the output stream.
            writeBackupData();
        } catch (FileNotFoundException e) {
            Slog.e(TAG, "Failed creating files for package " + mCurrentPackage.packageName
                    + " will ignore package. " + e);
        } finally {
            // We are either done, failed or have timed out, so do cleanup and kill the agent.
            cleanup();
        }
    }

    private void  prepareBackupFiles(String packageName) throws FileNotFoundException {

        // We pass a blank state to make sure we are getting the complete backup, not just an
        // increment
        mSavedState = ParcelFileDescriptor.open(mBlankStateName,
                MODE_READ_ONLY | MODE_CREATE);  // Make an empty file if necessary

        mBackupData = ParcelFileDescriptor.open(mBackupDataName,
                MODE_READ_WRITE | MODE_CREATE | MODE_TRUNCATE);

        if (!SELinux.restorecon(mBackupDataName)) {
            Slog.e(TAG, "SELinux restorecon failed on " + mBackupDataName);
        }

        mNewState = ParcelFileDescriptor.open(mNewStateName,
                MODE_READ_WRITE | MODE_CREATE | MODE_TRUNCATE);
    }

    private IBackupAgent bindToAgent(ApplicationInfo targetApp) {
        try {
            return mBackupManagerService.bindToAgentSynchronous(targetApp,
                    ApplicationThreadConstants.BACKUP_MODE_INCREMENTAL);
        } catch (SecurityException e) {
            Slog.e(TAG, "error in binding to agent for package " + targetApp.packageName
                    + ". " + e);
            return null;
        }
    }

    // Return true on backup success, false otherwise
    private boolean invokeAgentForAdbBackup(String packageName, IBackupAgent agent) {
        int token = mBackupManagerService.generateToken();
        try {
            mBackupManagerService.prepareOperationTimeout(token, TIMEOUT_BACKUP_INTERVAL, null,
                    OP_TYPE_BACKUP_WAIT);

            // Start backup and wait for BackupManagerService to get callback for success or timeout
            agent.doBackup(mSavedState, mBackupData, mNewState, Long.MAX_VALUE, token,
                    mBackupManagerService.mBackupManagerBinder);
            if (!mBackupManagerService.waitUntilOperationComplete(token)) {
                Slog.e(TAG, "Key-value backup failed on package " + packageName);
                return false;
            }
            if (DEBUG) {
                Slog.i(TAG, "Key-value backup success for package " + packageName);
            }
            return true;
        } catch (RemoteException e) {
            Slog.e(TAG, "Error invoking agent for backup on " + packageName + ". " + e);
            return false;
        }
    }

    class KeyValueAdbBackupDataCopier implements Runnable {
        private final PackageInfo mPackage;
        private final ParcelFileDescriptor mPipe;
        private final int mToken;

        KeyValueAdbBackupDataCopier(PackageInfo pack, ParcelFileDescriptor pipe,
                int token)
                throws IOException {
            mPackage = pack;
            mPipe = ParcelFileDescriptor.dup(pipe.getFileDescriptor());
            mToken = token;
        }

        @Override
        public void run() {
            try {
                FullBackupDataOutput output = new FullBackupDataOutput(mPipe);

                if (DEBUG) {
                    Slog.d(TAG, "Writing manifest for " + mPackage.packageName);
                }
                BackupManagerService.writeAppManifest(
                        mPackage, mPackageManager, mManifestFile, false, false);
                FullBackup.backupToTar(mPackage.packageName, FullBackup.KEY_VALUE_DATA_TOKEN, null,
                        mDataDir.getAbsolutePath(),
                        mManifestFile.getAbsolutePath(),
                        output);
                mManifestFile.delete();

                if (DEBUG) {
                    Slog.d(TAG, "Writing key-value package payload" + mPackage.packageName);
                }
                FullBackup.backupToTar(mPackage.packageName, FullBackup.KEY_VALUE_DATA_TOKEN, null,
                        mDataDir.getAbsolutePath(),
                        mBackupDataName.getAbsolutePath(),
                        output);

                // Write EOD marker
                try {
                    FileOutputStream out = new FileOutputStream(mPipe.getFileDescriptor());
                    byte[] buf = new byte[4];
                    out.write(buf);
                } catch (IOException e) {
                    Slog.e(TAG, "Unable to finalize backup stream!");
                }

                try {
                    mBackupManagerService.mBackupManagerBinder.opComplete(mToken, 0);
                } catch (RemoteException e) {
                    // we'll time out anyway, so we're safe
                }

            } catch (IOException e) {
                Slog.e(TAG, "Error running full backup for " + mPackage.packageName + ". " + e);
            } finally {
                IoUtils.closeQuietly(mPipe);
            }
        }
    }

    private void writeBackupData() throws IOException {

        int token = mBackupManagerService.generateToken();

        ParcelFileDescriptor[] pipes = null;
        try {
            pipes = ParcelFileDescriptor.createPipe();

            mBackupManagerService.prepareOperationTimeout(token, TIMEOUT_BACKUP_INTERVAL, null,
                    OP_TYPE_BACKUP_WAIT);

            // We will have to create a runnable that will read the manifest and backup data we
            // created, such that we can pipe the data into mOutput. The reason we do this is that
            // internally FullBackup.backupToTar is used, which will create the necessary file
            // header, but will also chunk the data. The method routeSocketDataToOutput in
            // BackupManagerService will dechunk the data, and append it to the TAR outputstream.
            KeyValueAdbBackupDataCopier runner = new KeyValueAdbBackupDataCopier(mCurrentPackage, pipes[1],
                    token);
            pipes[1].close();   // the runner has dup'd it
            pipes[1] = null;
            Thread t = new Thread(runner, "key-value-app-data-runner");
            t.start();

            // Now pull data from the app and stuff it into the output
            BackupManagerService.routeSocketDataToOutput(pipes[0], mOutput);

            if (!mBackupManagerService.waitUntilOperationComplete(token)) {
                Slog.e(TAG, "Full backup failed on package " + mCurrentPackage.packageName);
            } else {
                if (DEBUG) {
                    Slog.d(TAG, "Full package backup success: " + mCurrentPackage.packageName);
                }
            }
        } catch (IOException e) {
            Slog.e(TAG, "Error backing up " + mCurrentPackage.packageName + ": " + e);
        } finally {
            // flush after every package
            mOutput.flush();
            if (pipes != null) {
                IoUtils.closeQuietly(pipes[0]);
                IoUtils.closeQuietly(pipes[1]);
            }
        }
    }

    private void cleanup() {
        mBackupManagerService.tearDownAgentAndKill(mCurrentPackage.applicationInfo);
        mBlankStateName.delete();
        mNewStateName.delete();
        mBackupDataName.delete();
    }
}
Loading