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

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

Journal backup requests so that they won't be lost in a crash

When an application requests a backup via dataChanged(), we now journal that
fact on disk.  The journal persists and is only removed following a successful
backup pass.  When the backup manager is started at boot time, it looks for any
existing journal files and schedules a backup for the apps listed in them, on
the expectation that the device shut down or crashed before a backup could be
performed.
parent 0b774530
Loading
Loading
Loading
Loading
+94 −5
Original line number Diff line number Diff line
@@ -51,11 +51,13 @@ import com.android.internal.backup.LocalTransport;
import com.android.internal.backup.GoogleTransport;
import com.android.internal.backup.IBackupTransport;

import java.io.EOFException;
import java.io.File;
import java.io.FileDescriptor;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.RandomAccessFile;
import java.lang.String;
import java.util.ArrayList;
import java.util.HashMap;
@@ -122,6 +124,9 @@ class BackupManagerService extends IBackupManager.Stub {

    private File mStateDir;
    private File mDataDir;
    private File mJournalDir;
    private File mJournal;
    private RandomAccessFile mJournalStream;
    
    public BackupManagerService(Context context) {
        mContext = context;
@@ -133,6 +138,11 @@ class BackupManagerService extends IBackupManager.Stub {
        mStateDir.mkdirs();
        mDataDir = Environment.getDownloadCacheDirectory();

        // Set up the backup-request journaling
        mJournalDir = new File(mStateDir, "pending");
        mJournalDir.mkdirs();
        makeJournalLocked();    // okay because no other threads are running yet

        //!!! TODO: default to cloud transport, not local
        mTransportId = BackupManager.TRANSPORT_LOCAL;
        
@@ -141,6 +151,10 @@ class BackupManagerService extends IBackupManager.Stub {
            addPackageParticipantsLocked(null);
        }

        // Now that we know about valid backup participants, parse any
        // leftover journal files and schedule a new backup pass
        parseLeftoverJournals();

        // Register for broadcasts about package install, etc., so we can
        // update the provider list.
        IntentFilter filter = new IntentFilter();
@@ -150,6 +164,46 @@ class BackupManagerService extends IBackupManager.Stub {
        mContext.registerReceiver(mBroadcastReceiver, filter);
    }

    private void makeJournalLocked() {
        try {
            mJournal = File.createTempFile("journal", null, mJournalDir);
            mJournalStream = new RandomAccessFile(mJournal, "rwd");
        } catch (IOException e) {
            Log.e(TAG, "Unable to write backup journals");
            mJournal = null;
            mJournalStream = null;
        }
    }

    private void parseLeftoverJournals() {
        if (mJournal != null) {
            File[] allJournals = mJournalDir.listFiles();
            for (File f : allJournals) {
                if (f.compareTo(mJournal) != 0) {
                    // This isn't the current journal, so it must be a leftover.  Read
                    // out the package names mentioned there and schedule them for
                    // backup.
                    try {
                        Log.i(TAG, "Found stale backup journal, scheduling:");
                        RandomAccessFile in = new RandomAccessFile(f, "r");
                        while (true) {
                            String packageName = in.readUTF();
                            Log.i(TAG, "    + " + packageName);
                            dataChanged(packageName);
                        }
                    } catch (EOFException e) {
                        // no more data; we're done
                    } catch (Exception e) {
                        // can't read it or other error; just skip it
                    } finally {
                        // close/delete the file
                        f.delete();
                    }
                }
            }
        }
    }

    // ----- Track installation/removal of packages -----
    BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
        public void onReceive(Context context, Intent intent) {
@@ -198,6 +252,8 @@ class BackupManagerService extends IBackupManager.Stub {
            switch (msg.what) {
            case MSG_RUN_BACKUP:
                // snapshot the pending-backup set and work on that
                File oldJournal = mJournal;
                RandomAccessFile oldJournalStream = mJournalStream;
                synchronized (mQueueLock) {
                    if (mBackupQueue == null) {
                        mBackupQueue = new ArrayList<BackupRequest>();
@@ -207,10 +263,22 @@ class BackupManagerService extends IBackupManager.Stub {
                        mPendingBackups = new HashMap<ApplicationInfo,BackupRequest>();
                    }
                    // !!! TODO: start a new backup-queue journal file too
                    // WARNING: If we crash after this line, anything in mPendingBackups will
                    // be lost.  FIX THIS.
                    if (mJournalStream != null) {
                        try {
                            mJournalStream.close();
                        } catch (IOException e) {
                            // don't need to do anything
                        }
                        makeJournalLocked();
                    }

                    // At this point, we have started a new journal file, and the old
                    // file identity is being passed to the backup processing thread.
                    // When it completes successfully, that old journal file will be
                    // deleted.  If we crash prior to that, the old journal is parsed
                    // at next boot and the journaled requests fulfilled.
                }
                (new PerformBackupThread(mTransportId, mBackupQueue)).run();
                (new PerformBackupThread(mTransportId, mBackupQueue, oldJournal)).run();
                break;

            case MSG_RUN_FULL_BACKUP:
@@ -433,10 +501,13 @@ class BackupManagerService extends IBackupManager.Stub {
        private static final String TAG = "PerformBackupThread";
        int mTransport;
        ArrayList<BackupRequest> mQueue;
        File mJournal;

        public PerformBackupThread(int transportId, ArrayList<BackupRequest> queue) {
        public PerformBackupThread(int transportId, ArrayList<BackupRequest> queue,
                File journal) {
            mTransport = transportId;
            mQueue = queue;
            mJournal = journal;
        }

        @Override
@@ -468,6 +539,10 @@ class BackupManagerService extends IBackupManager.Stub {
                Log.e(TAG, "Error ending transport");
                e.printStackTrace();
            }

            if (!mJournal.delete()) {
                Log.e(TAG, "Unable to remove backup journal file " + mJournal.getAbsolutePath());
            }
        }

        private void doQueuedBackups(IBackupTransport transport) {
@@ -782,7 +857,9 @@ class BackupManagerService extends IBackupManager.Stub {
                        // one already there, then overwrite it, but no harm done.
                        BackupRequest req = new BackupRequest(app, false);
                        mPendingBackups.put(app, req);
                        // !!! TODO: write to the pending-backup journal file in case of crash

                        // Journal this request in case of crash
                        writeToJournalLocked(packageName);
                    }
                }

@@ -804,6 +881,18 @@ class BackupManagerService extends IBackupManager.Stub {
        }
    }

    private void writeToJournalLocked(String str) {
        if (mJournalStream != null) {
            try {
                mJournalStream.writeUTF(str);
            } catch (IOException e) {
                Log.e(TAG, "Error writing to backup journal");
                mJournalStream = null;
                mJournal = null;
            }
        }
    }

    // Schedule a backup pass for a given package.  This method will schedule a
    // full backup even for apps that do not declare an android:backupAgent, so
    // use with care.