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

Commit 0144516e authored by Dan Egnor's avatar Dan Egnor
Browse files

Make IBackupTransport.finishBackup() also return an int code, since it too can

return TRANSPORT_NOT_INITIALIZED (in fact that's typically how it comes).

For consistency, make other IBackupTransport methods return int instead of
boolean, and handle accordingly.

Make initializeDevice() its own method instead of a flag on performBackup().
This will be needed when un-checking the settings box anyway, and is
conceptually unrelated to whatever happens to be the first post-initialization
backup we perform.  (Note that even if the init is sent separately from the
backup operation, the server will remember that an init has been done and
will *not* return NOT_INITIALIZED for the subsequent backup.)

Fix LocalTransport accordingly (trivial changes).

Handle failures more robustly in BackupManagerService -- most notably,
doQueuedBackups() was ignoring the result code of processOneBackup(), so
a NOT_INITIALIZED error would go past unseen (at least until the next
backup pass).  Keep track of error code returns more universally now.
(This includes finishBackup(), of course.)
parent 9568fb21
Loading
Loading
Loading
Loading
+26 −38
Original line number Diff line number Diff line
@@ -22,24 +22,6 @@ import android.os.ParcelFileDescriptor;

/** {@hide} */
interface IBackupTransport {
/* STOPSHIP - don't ship with this comment in place
    Things the transport interface has to do:
    1. set up the connection to the destination
        - set up encryption
        - for Google cloud, log in using the user's gaia credential or whatever
        - for adb, just set up the all-in-one destination file
    2. send each app's backup transaction
        - parse the data file for key/value pointers etc
        - send key/blobsize set to the Google cloud, get back quota ok/rejected response
        - sd/adb doesn't preflight; no per-app quota
        - app's entire change is essentially atomic
        - cloud transaction encrypts then sends each key/value pair separately; we already
          parsed the data when preflighting so we don't have to again here
        - sd target streams raw data into encryption envelope then to sd?
    3. shut down connection to destination
        - cloud: tear down connection etc
        - adb: close the file
*/
    /**
     * Ask the transport where, on local device storage, to keep backup state blobs.
     * This is per-transport so that mock transports used for testing can coexist with
@@ -67,6 +49,17 @@ interface IBackupTransport {
     */
    long requestBackupTime();

    /**
     * Initialize the server side storage for this device, erasing all stored data.
     * The transport may send the request immediately, or may buffer it.  After
     * this is called, {@link #finishBackup} must be called to ensure the request
     * is sent and received successfully.
     *
     * @return One of {@link BackupConstants#TRANSPORT_OK} (OK so far) or
     *   {@link BackupConstants#TRANSPORT_ERROR} (on network error or other failure).
     */
    int initializeDevice();

    /**
     * Send one application's data to the backup destination.  The transport may send
     * the data immediately, or may buffer it.  After this is called, {@link #finishBackup}
@@ -81,15 +74,12 @@ interface IBackupTransport {
     *   will be erased prior to the storage of the data provided here.  The purpose of this
     *   is to provide a guarantee that no stale data exists in the restore set when the
     *   device begins providing backups.
     * @return If everything is okay so far, returns zero (but {@link #finishBackup} must
     *   still be called).  If the backend dataset has unexpectedly become unavailable,
     *   such as when it is deleted after a period of device inactivity, returns {@link
     *   BackupManager#DATASET_UNAVAILABLE}; in this case, the transport should be
     *   reinitalized and the entire backup pass restarted.  Any other nonzero value is a
     *   fatal error requiring that this package's backup be aborted and rescheduled.
     * @return one of {@link BackupConstants#TRANSPORT_OK} (OK so far),
     *  {@link BackupConstants#TRANSPORT_ERROR} (on network error or other failure), or
     *  {@link BackupConstants#TRANSPORT_NOT_INITIALIZED} (if the backend dataset has
     *  become lost due to inactive expiry or some other reason and needs re-initializing)
     */
    int performBackup(in PackageInfo packageInfo, in ParcelFileDescriptor inFd,
            boolean wipeAllFirst);
    int performBackup(in PackageInfo packageInfo, in ParcelFileDescriptor inFd);

    /**
     * Erase the give application's data from the backup destination.  This clears
@@ -97,10 +87,9 @@ interface IBackupTransport {
     * the app had never yet been backed up.  After this is called, {@link finishBackup}
     * must be called to ensure that the operation is recorded successfully.
     *
     * @return false if errors occurred (the backup should be aborted and rescheduled),
     *   true if everything is OK so far (but {@link #finishBackup} must be called).
     * @return the same error codes as {@link #performBackup}.
     */
    boolean clearBackupData(in PackageInfo packageInfo);
    int clearBackupData(in PackageInfo packageInfo);

    /**
     * Finish sending application data to the backup destination.  This must be
@@ -108,10 +97,9 @@ interface IBackupTransport {
     * all data is sent.  Only when this method returns true can a backup be assumed
     * to have succeeded.
     *
     * @return false if errors occurred (the backup should be aborted and rescheduled),
     *   true if everything is OK.
     * @return the same error codes as {@link #performBackup}.
     */
    boolean finishBackup();
    int finishBackup();

    /**
     * Get the set of backups currently available over this transport.
@@ -129,10 +117,11 @@ interface IBackupTransport {
     * @param token A backup token as returned by {@link #getAvailableRestoreSets}.
     * @param packages List of applications to restore (if data is available).
     *   Application data will be restored in the order given.
     * @return false if errors occurred (the restore should be aborted and rescheduled),
     *   true if everything is OK so far (go ahead and call {@link #nextRestorePackage}).
     * @return One of {@link BackupConstants#TRANSPORT_OK} (OK so far, call
     *   {@link #nextRestorePackage}) or {@link BackupConstants#TRANSPORT_ERROR}
     *   (an error occurred, the restore should be aborted and rescheduled).
     */
    boolean startRestore(long token, in PackageInfo[] packages);
    int startRestore(long token, in PackageInfo[] packages);

    /**
     * Get the package name of the next application with data in the backup store.
@@ -145,10 +134,9 @@ interface IBackupTransport {
    /**
     * Get the data for the application returned by {@link #nextRestorePackage}.
     * @param data An open, writable file into which the backup data should be stored.
     * @return false if errors occurred (the restore should be aborted and rescheduled),
     *   true if everything is OK so far (go ahead and call {@link #nextRestorePackage}).
     * @return the same error codes as {@link #nextRestorePackage}.
     */
    boolean getRestoreData(in ParcelFileDescriptor outFd);
    int getRestoreData(in ParcelFileDescriptor outFd);

    /**
     * End a restore session (aborting any in-process data transfer as necessary),
+20 −19
Original line number Diff line number Diff line
@@ -47,25 +47,26 @@ public class LocalTransport extends IBackupTransport.Stub {
    }


    public String transportDirName() throws RemoteException {
    public String transportDirName() {
        return TRANSPORT_DIR_NAME;
    }

    public long requestBackupTime() throws RemoteException {
    public long requestBackupTime() {
        // any time is a good time for local backup
        return 0;
    }

    public int performBackup(PackageInfo packageInfo, ParcelFileDescriptor data,
            boolean wipeAllFirst) throws RemoteException {
    public int initializeDevice() {
        if (DEBUG) Log.v(TAG, "wiping all data");
        deleteContents(mDataDir);
        return BackupConstants.TRANSPORT_OK;
    }

    public int performBackup(PackageInfo packageInfo, ParcelFileDescriptor data) {
        if (DEBUG) Log.v(TAG, "performBackup() pkg=" + packageInfo.packageName);

        File packageDir = new File(mDataDir, packageInfo.packageName);
        packageDir.mkdirs();
        if (wipeAllFirst) {
            if (DEBUG) Log.v(TAG, "wiping all data first");
            deleteContents(mDataDir);
        }

        // Each 'record' in the restore set is kept in its own file, named by
        // the record key.  Wind through the data file, extracting individual
@@ -130,7 +131,7 @@ public class LocalTransport extends IBackupTransport.Stub {
        }
    }

    public boolean clearBackupData(PackageInfo packageInfo) {
    public int clearBackupData(PackageInfo packageInfo) {
        if (DEBUG) Log.v(TAG, "clearBackupData() pkg=" + packageInfo.packageName);

        File packageDir = new File(mDataDir, packageInfo.packageName);
@@ -138,12 +139,12 @@ public class LocalTransport extends IBackupTransport.Stub {
            f.delete();
        }
        packageDir.delete();
        return true;
        return BackupConstants.TRANSPORT_OK;
    }

    public boolean finishBackup() throws RemoteException {
    public int finishBackup() {
        if (DEBUG) Log.v(TAG, "finishBackup()");
        return true;
        return BackupConstants.TRANSPORT_OK;
    }

    // Restore handling
@@ -154,11 +155,11 @@ public class LocalTransport extends IBackupTransport.Stub {
        return array;
    }

    public boolean startRestore(long token, PackageInfo[] packages) {
    public int startRestore(long token, PackageInfo[] packages) {
        if (DEBUG) Log.v(TAG, "start restore " + token);
        mRestorePackages = packages;
        mRestorePackage = -1;
        return true;
        return BackupConstants.TRANSPORT_OK;
    }

    public String nextRestorePackage() {
@@ -175,7 +176,7 @@ public class LocalTransport extends IBackupTransport.Stub {
        return "";
    }

    public boolean getRestoreData(ParcelFileDescriptor outFd) {
    public int getRestoreData(ParcelFileDescriptor outFd) {
        if (mRestorePackages == null) throw new IllegalStateException("startRestore not called");
        if (mRestorePackage < 0) throw new IllegalStateException("nextRestorePackage not called");
        File packageDir = new File(mDataDir, mRestorePackages[mRestorePackage].packageName);
@@ -183,9 +184,9 @@ public class LocalTransport extends IBackupTransport.Stub {
        // The restore set is the concatenation of the individual record blobs,
        // each of which is a file in the package's directory
        File[] blobs = packageDir.listFiles();
        if (blobs == null) {
        if (blobs == null) {  // nextRestorePackage() ensures the dir exists, so this is an error
            Log.e(TAG, "Error listing directory: " + packageDir);
            return false;  // nextRestorePackage() ensures the dir exists, so this is an error
            return BackupConstants.TRANSPORT_ERROR;
        }

        // We expect at least some data if the directory exists in the first place
@@ -206,10 +207,10 @@ public class LocalTransport extends IBackupTransport.Stub {
                    in.close();
                }
            }
            return true;
            return BackupConstants.TRANSPORT_OK;
        } catch (IOException e) {
            Log.e(TAG, "Unable to read backup records", e);
            return false;
            return BackupConstants.TRANSPORT_ERROR;
        }
    }

+70 −63
Original line number Diff line number Diff line
@@ -920,53 +920,57 @@ class BackupManagerService extends IBackupManager.Stub {

            try {
                EventLog.writeEvent(BACKUP_START_EVENT, mTransport.transportDirName());
                int status = BackupConstants.TRANSPORT_OK;

                // If we haven't stored anything yet, we need to do an init operation.
                if (status == BackupConstants.TRANSPORT_OK && mEverStoredApps.size() == 0) {
                    status = mTransport.initializeDevice();
                }

                // The package manager doesn't have a proper <application> etc, but since
                // it's running here in the system process we can just set up its agent
                // directly and use a synthetic BackupRequest.  We always run this pass
                // because it's cheap and this way we guarantee that we don't get out of
                // step even if we're selecting among various transports at run time.
                if (status == BackupConstants.TRANSPORT_OK) {
                    PackageManagerBackupAgent pmAgent = new PackageManagerBackupAgent(
                            mPackageManager, allAgentPackages());
                    BackupRequest pmRequest = new BackupRequest(new ApplicationInfo(), false);
                    pmRequest.appInfo.packageName = PACKAGE_MANAGER_SENTINEL;

                // If we haven't stored anything yet, we need to do an init
                // operation along with recording the metadata blob.
                boolean needInit = (mEverStoredApps.size() == 0);
                int result = processOneBackup(pmRequest,
                        IBackupAgent.Stub.asInterface(pmAgent.onBind()),
                        mTransport, needInit);
                if (result == BackupConstants.TRANSPORT_NOT_INITIALIZED) {
                    // The backend reports that our dataset has been wiped.  We need to
                    // reset all of our bookkeeping and instead run a new backup pass for
                    // everything.
                    EventLog.writeEvent(BACKUP_RESET_EVENT, mTransport.transportDirName());
                    resetBackupState(mStateDir);
                    backupNow();
                    return;
                } else if (result != BackupConstants.TRANSPORT_OK) {
                    // Give up if we couldn't even process the metadata
                    Log.e(TAG, "Meta backup err " + result);
                    return;
                    status = processOneBackup(pmRequest,
                            IBackupAgent.Stub.asInterface(pmAgent.onBind()), mTransport);
                }

                if (status == BackupConstants.TRANSPORT_OK) {
                    // Now run all the backups in our queue
                int count = mQueue.size();
                doQueuedBackups(mTransport);
                    status = doQueuedBackups(mTransport);
                }

                // Finally, tear down the transport
                if (mTransport.finishBackup()) {
                if (status == BackupConstants.TRANSPORT_OK) {
                    // Tell the transport to finish everything it has buffered
                    status = mTransport.finishBackup();
                    if (status == BackupConstants.TRANSPORT_OK) {
                        int millis = (int) (SystemClock.elapsedRealtime() - startRealtime);
                    EventLog.writeEvent(BACKUP_SUCCESS_EVENT, count, millis);
                        EventLog.writeEvent(BACKUP_SUCCESS_EVENT, mQueue.size(), millis);
                    } else {
                        EventLog.writeEvent(BACKUP_TRANSPORT_FAILURE_EVENT, "");
                        Log.e(TAG, "Transport error in finishBackup()");
                    }
                }

                if (!mJournal.delete()) {
                // When we succeed at everything, we can remove the journal
                if (status == BackupConstants.TRANSPORT_OK && !mJournal.delete()) {
                    Log.e(TAG, "Unable to remove backup journal file " + mJournal);
                }

                if (status == BackupConstants.TRANSPORT_NOT_INITIALIZED) {
                    // The backend reports that our dataset has been wiped.  We need to
                    // reset all of our bookkeeping and instead run a new backup pass for
                    // everything.
                    EventLog.writeEvent(BACKUP_RESET_EVENT, mTransport.transportDirName());
                    resetBackupState(mStateDir);
                    backupNow();
                }
            } catch (Exception e) {
                Log.e(TAG, "Error in backup thread", e);
            } finally {
@@ -975,7 +979,7 @@ class BackupManagerService extends IBackupManager.Stub {
            }
        }

        private void doQueuedBackups(IBackupTransport transport) {
        private int doQueuedBackups(IBackupTransport transport) {
            for (BackupRequest request : mQueue) {
                Log.d(TAG, "starting agent for backup of " + request);

@@ -995,25 +999,26 @@ class BackupManagerService extends IBackupManager.Stub {
                try {
                    agent = bindToAgentSynchronous(request.appInfo, mode);
                    if (agent != null) {
                        processOneBackup(request, agent, transport, false);
                        int result = processOneBackup(request, agent, transport);
                        if (result != BackupConstants.TRANSPORT_OK) return result;
                    }

                    // unbind even on timeout, just in case
                    mActivityManager.unbindBackupAgent(request.appInfo);
                } catch (SecurityException ex) {
                    // Try for the next one.
                    Log.d(TAG, "error in bind/backup", ex);
                } catch (RemoteException e) {
                    Log.v(TAG, "bind/backup threw");
                    e.printStackTrace();
                } finally {
                    try {  // unbind even on timeout, just in case
                        mActivityManager.unbindBackupAgent(request.appInfo);
                    } catch (RemoteException e) {}
                }
            }

            return BackupConstants.TRANSPORT_OK;
        }

        int processOneBackup(BackupRequest request, IBackupAgent agent,
                IBackupTransport transport, boolean doInit) {
        private int processOneBackup(BackupRequest request, IBackupAgent agent,
                IBackupTransport transport) {
            final String packageName = request.appInfo.packageName;
            if (DEBUG) Log.d(TAG, "processOneBackup doBackup(" + doInit + ") on " + packageName);
            if (DEBUG) Log.d(TAG, "processOneBackup doBackup() on " + packageName);

            File savedStateName = new File(mStateDir, packageName);
            File backupDataName = new File(mDataDir, packageName + ".data");
@@ -1073,26 +1078,23 @@ class BackupManagerService extends IBackupManager.Stub {
            }

            // Now propagate the newly-backed-up data to the transport
            int result = BackupConstants.TRANSPORT_OK;
            try {
                int size = (int) backupDataName.length();
                if (size > 0) {
                    if (result == BackupConstants.TRANSPORT_OK) {
                        backupData = ParcelFileDescriptor.open(backupDataName,
                                ParcelFileDescriptor.MODE_READ_ONLY);
                        result = transport.performBackup(packInfo, backupData);
                    }

                    // TODO - We call finishBackup() for each application backed up, because
                    // we need to know now whether it succeeded or failed.  Instead, we should
                    // hold off on finishBackup() until the end, which implies holding off on
                    // renaming *all* the output state files (see below) until that happens.

                    int performOkay = transport.performBackup(packInfo, backupData, doInit);
                    if (performOkay == BackupConstants.TRANSPORT_NOT_INITIALIZED) {
                        Log.i(TAG, "Backend not initialized");
                        return performOkay;
                    }

                    if ((performOkay != 0) ||
                        !transport.finishBackup()) {
                        throw new Exception("Backup transport failed");
                    if (result == BackupConstants.TRANSPORT_OK) {
                        result = transport.finishBackup();
                    }
                } else {
                    if (DEBUG) Log.i(TAG, "no backup data written; not calling transport");
@@ -1101,18 +1103,22 @@ class BackupManagerService extends IBackupManager.Stub {
                // After successful transport, delete the now-stale data
                // and juggle the files so that next time we supply the agent
                // with the new state file it just created.
                if (result == BackupConstants.TRANSPORT_OK) {
                    backupDataName.delete();
                    newStateName.renameTo(savedStateName);
                    EventLog.writeEvent(BACKUP_PACKAGE_EVENT, packageName, size);
                } else {
                    EventLog.writeEvent(BACKUP_TRANSPORT_FAILURE_EVENT, packageName);
                }
            } catch (Exception e) {
                Log.e(TAG, "Transport error backing up " + packageName, e);
                EventLog.writeEvent(BACKUP_TRANSPORT_FAILURE_EVENT, packageName);
                return BackupConstants.TRANSPORT_ERROR;
                result = BackupConstants.TRANSPORT_ERROR;
            } finally {
                try { if (backupData != null) backupData.close(); } catch (IOException e) {}
            }

            return BackupConstants.TRANSPORT_OK;
            return result;
        }
    }

@@ -1237,7 +1243,8 @@ class BackupManagerService extends IBackupManager.Stub {
                    }
                }

                if (!mTransport.startRestore(mToken, restorePackages.toArray(new PackageInfo[0]))) {
                if (mTransport.startRestore(mToken, restorePackages.toArray(new PackageInfo[0])) !=
                        BackupConstants.TRANSPORT_OK) {
                    Log.e(TAG, "Error starting restore operation");
                    EventLog.writeEvent(RESTORE_TRANSPORT_FAILURE_EVENT);
                    return;
@@ -1437,7 +1444,7 @@ class BackupManagerService extends IBackupManager.Stub {
                            ParcelFileDescriptor.MODE_CREATE |
                            ParcelFileDescriptor.MODE_TRUNCATE);

                if (!mTransport.getRestoreData(backupData)) {
                if (mTransport.getRestoreData(backupData) != BackupConstants.TRANSPORT_OK) {
                    Log.e(TAG, "Error getting restore data for " + packageName);
                    EventLog.writeEvent(RESTORE_TRANSPORT_FAILURE_EVENT);
                    return;