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

Commit 46e462a3 authored by Android (Google) Code Review's avatar Android (Google) Code Review
Browse files

Merge change I852f8e46 into eclair

* changes:
  Only re-initialize backup state if @pm@ metadata is missing, to defensively work around a still-mysterious bug where the list of saved packages ends up being empty even though we still have state pending.  If we do re-initialize, then wipe all state to make sure the right thing happens.
parents 935cbda0 852f8e46
Loading
Loading
Loading
Loading
+147 −180
Original line number Diff line number Diff line
@@ -205,11 +205,9 @@ class BackupManagerService extends IBackupManager.Stub {
    File mDataDir;
    File mJournalDir;
    File mJournal;
    RandomAccessFile mJournalStream;

    // Keep a log of all the apps we've ever backed up
    private File mEverStored;
    private RandomAccessFile mEverStoredStream;
    HashSet<String> mEverStoredApps = new HashSet<String>();

    // Persistently track the need to do a full init
@@ -257,7 +255,7 @@ class BackupManagerService extends IBackupManager.Stub {
        // Set up the backup-request journaling
        mJournalDir = new File(mBaseStateDir, "pending");
        mJournalDir.mkdirs();   // creates mBaseStateDir along the way
        makeJournalLocked();    // okay because no other threads are running yet
        mJournal = null;        // will be created on first use

        // Set up the various sorts of package tracking we do
        initPackageTracking();
@@ -369,20 +367,27 @@ class BackupManagerService extends IBackupManager.Stub {
        // this log, we sanity-check its contents here and reconstruct it.
        mEverStored = new File(mBaseStateDir, "processed");
        File tempProcessedFile = new File(mBaseStateDir, "processed.new");
        try {

        // If we were in the middle of removing something from the ever-backed-up
        // file, there might be a transient "processed.new" file still present.
        // Ignore it -- we'll validate "processed" against the current package set.
        if (tempProcessedFile.exists()) {
            tempProcessedFile.delete();
        }

        // If there are previous contents, parse them out then start a new
        // file to continue the recordkeeping.
        if (mEverStored.exists()) {
                RandomAccessFile temp = new RandomAccessFile(tempProcessedFile, "rw");
                mEverStoredStream = new RandomAccessFile(mEverStored, "r");
            RandomAccessFile temp = null;
            RandomAccessFile in = null;

                // parse its existing contents
                mEverStoredStream.seek(0);
                temp.seek(0);
            try {
                temp = new RandomAccessFile(tempProcessedFile, "rws");
                in = new RandomAccessFile(mEverStored, "r");

                while (true) {
                    PackageInfo info;
                        String pkg = mEverStoredStream.readUTF();
                    String pkg = in.readUTF();
                    try {
                        info = mPackageManager.getPackageInfo(pkg, 0);
                        mEverStoredApps.add(pkg);
@@ -394,29 +399,17 @@ class BackupManagerService extends IBackupManager.Stub {
                    }
                }
            } catch (EOFException e) {
                    // now we're at EOF
                }

                // Once we've rewritten the backup history log, atomically replace the
                // old one with the new one then reopen the file for continuing use.
                temp.close();
                mEverStoredStream.close();
                tempProcessedFile.renameTo(mEverStored);
                if (!tempProcessedFile.renameTo(mEverStored)) {
                    Log.e(TAG, "Error renaming " + tempProcessedFile + " to " + mEverStored);
                }
            // This will create the file if it doesn't exist
            mEverStoredStream = new RandomAccessFile(mEverStored, "rwd");
            mEverStoredStream.seek(mEverStoredStream.length());
            } catch (IOException e) {
            Log.e(TAG, "Unable to open known-stored file!");
            mEverStoredStream = null;
                Log.e(TAG, "Error in processed file", e);
            } finally {
                try { if (temp != null) temp.close(); } catch (IOException e) {}
                try { if (in != null) in.close(); } catch (IOException e) {}
            }

        // If we were in the middle of removing something from the ever-backed-up
        // file, there might be a transient "processed.new" file still present.
        // We've reconstructed a coherent state at this point though, so we can
        // safely discard that file now.
        if (tempProcessedFile.exists()) {
            tempProcessedFile.delete();
        }

        // Register for broadcasts about package install, etc., so we can
@@ -428,28 +421,16 @@ 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) {
        for (File f : mJournalDir.listFiles()) {
            if (mJournal == null || 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.
                RandomAccessFile in = null;
                try {
                    Log.i(TAG, "Found stale backup journal, scheduling:");
                        RandomAccessFile in = new RandomAccessFile(f, "r");
                    in = new RandomAccessFile(f, "r");
                    while (true) {
                        String packageName = in.readUTF();
                        Log.i(TAG, "    + " + packageName);
@@ -458,15 +439,15 @@ class BackupManagerService extends IBackupManager.Stub {
                } catch (EOFException e) {
                    // no more data; we're done
                } catch (Exception e) {
                        // can't read it or other error; just skip it
                    Log.e(TAG, "Can't read " + f, e);
                } finally {
                    // close/delete the file
                    try { if (in != null) in.close(); } catch (IOException e) {}
                    f.delete();
                }
            }
        }
    }
    }

    // Maintain persistent state around whether need to do an initialize operation.
    // Must be called with the queue lock held.
@@ -505,19 +486,8 @@ class BackupManagerService extends IBackupManager.Stub {
    void resetBackupState(File stateFileDir) {
        synchronized (mQueueLock) {
            // Wipe the "what we've ever backed up" tracking
            try {
                // close the ever-stored journal...
                if (mEverStoredStream != null) {
                    mEverStoredStream.close();
                }
                // ... so we can delete it and start over
                mEverStored.delete();
                mEverStoredStream = new RandomAccessFile(mEverStored, "rwd");
            } catch (IOException e) {
                Log.e(TAG, "Unable to open known-stored file!");
                mEverStoredStream = null;
            }
            mEverStoredApps.clear();
            mEverStored.delete();

            // Remove all the state files
            for (File sf : stateFileDir.listFiles()) {
@@ -533,11 +503,7 @@ class BackupManagerService extends IBackupManager.Stub {
                int uid = mBackupParticipants.keyAt(i);
                HashSet<ApplicationInfo> participants = mBackupParticipants.valueAt(i);
                for (ApplicationInfo app: participants) {
                    try {
                    dataChanged(app.packageName);
                    } catch (RemoteException e) {
                        // can't happen; we're in the same process
                    }
                }
            }
        }
@@ -652,7 +618,6 @@ class BackupManagerService extends IBackupManager.Stub {

                // snapshot the pending-backup set and work on that
                ArrayList<BackupRequest> queue = new ArrayList<BackupRequest>();
                File oldJournal = mJournal;
                synchronized (mQueueLock) {
                    // Do we have any work to do?
                    if (mPendingBackups.size() > 0) {
@@ -663,14 +628,8 @@ class BackupManagerService extends IBackupManager.Stub {
                        mPendingBackups.clear();

                        // Start a new backup-queue journal file too
                        if (mJournalStream != null) {
                            try {
                                mJournalStream.close();
                            } catch (IOException e) {
                                // don't need to do anything
                            }
                            makeJournalLocked();
                        }
                        File oldJournal = mJournal;
                        mJournal = null;

                        // At this point, we have started a new journal file, and the old
                        // file identity is being passed to the backup processing thread.
@@ -763,11 +722,7 @@ class BackupManagerService extends IBackupManager.Stub {
                if (!mEverStoredApps.contains(pkg.packageName)) {
                    if (DEBUG) Log.i(TAG, "New app " + pkg.packageName
                            + " never backed up; scheduling");
                    try {
                    dataChanged(pkg.packageName);
                    } catch (RemoteException e) {
                        // can't happen; it's a local method call
                    }
                }
            }
        }
@@ -871,58 +826,57 @@ class BackupManagerService extends IBackupManager.Stub {
    // Called from the backup thread: record that the given app has been successfully
    // backed up at least once
    void logBackupComplete(String packageName) {
        if (mEverStoredStream != null && !packageName.equals(PACKAGE_MANAGER_SENTINEL)) {
        if (packageName.equals(PACKAGE_MANAGER_SENTINEL)) return;

        synchronized (mEverStoredApps) {
                if (mEverStoredApps.add(packageName)) {
            if (!mEverStoredApps.add(packageName)) return;

            RandomAccessFile out = null;
            try {
                        mEverStoredStream.writeUTF(packageName);
                out = new RandomAccessFile(mEverStored, "rws");
                out.seek(out.length());
                out.writeUTF(packageName);
            } catch (IOException e) {
                        Log.e(TAG, "Unable to log backup of " + packageName + ", ceasing log");
                        try {
                            mEverStoredStream.close();
                        } catch (IOException ioe) {
                            // we're dropping it; no need to handle an exception on close here
                        }
                        mEverStoredStream = null;
                    }
                }
                Log.e(TAG, "Can't log backup of " + packageName + " to " + mEverStored);
            } finally {
                try { if (out != null) out.close(); } catch (IOException e) {}
            }
        }
    }

    // Remove our awareness of having ever backed up the given package
    void removeEverBackedUp(String packageName) {
        if (DEBUG) Log.v(TAG, "Removing backed-up knowledge of " + packageName
                + ", new set:");
        if (DEBUG) Log.v(TAG, "Removing backed-up knowledge of " + packageName + ", new set:");

        if (mEverStoredStream != null) {
        synchronized (mEverStoredApps) {
            // Rewrite the file and rename to overwrite.  If we reboot in the middle,
            // we'll recognize on initialization time that the package no longer
            // exists and fix it up then.
            File tempKnownFile = new File(mBaseStateDir, "processed.new");
            RandomAccessFile known = null;
            try {
                    mEverStoredStream.close();
                    RandomAccessFile known = new RandomAccessFile(tempKnownFile, "rw");
                known = new RandomAccessFile(tempKnownFile, "rws");
                mEverStoredApps.remove(packageName);
                for (String s : mEverStoredApps) {
                    known.writeUTF(s);
                    if (DEBUG) Log.v(TAG, "    " + s);
                }
                known.close();
                    tempKnownFile.renameTo(mEverStored);
                    mEverStoredStream = new RandomAccessFile(mEverStored, "rwd");
                known = null;
                if (!tempKnownFile.renameTo(mEverStored)) {
                    throw new IOException("Can't rename " + tempKnownFile + " to " + mEverStored);
                }
            } catch (IOException e) {
                // Bad: we couldn't create the new copy.  For safety's sake we
                // abandon the whole process and remove all what's-backed-up
                // state entirely, meaning we'll force a backup pass for every
                // participant on the next boot or [re]install.
                    Log.w(TAG, "Error rewriting backed-up set; halting log");
                    mEverStoredStream = null;
                Log.w(TAG, "Error rewriting " + mEverStored, e);
                mEverStoredApps.clear();
                tempKnownFile.delete();
                mEverStored.delete();
                }
            } finally {
                try { if (known != null) known.close(); } catch (IOException e) {}
            }
        }
    }
@@ -1018,8 +972,7 @@ class BackupManagerService extends IBackupManager.Stub {
    }

    class ClearDataObserver extends IPackageDataObserver.Stub {
        public void onRemoveCompleted(String packageName, boolean succeeded)
                throws RemoteException {
        public void onRemoveCompleted(String packageName, boolean succeeded) {
            synchronized(mClearDataLock) {
                mClearingData = false;
                mClearDataLock.notifyAll();
@@ -1061,9 +1014,11 @@ class BackupManagerService extends IBackupManager.Stub {
            try {
                EventLog.writeEvent(BACKUP_START_EVENT, mTransport.transportDirName());

                // If we haven't stored anything yet, we need to do an init operation.
                if (status == BackupConstants.TRANSPORT_OK && mEverStoredApps.size() == 0) {
                    Log.i(TAG, "Initializing (wiping) backup transport storage");
                // If we haven't stored package manager metadata yet, we must init the transport.
                File pmState = new File(mStateDir, PACKAGE_MANAGER_SENTINEL);
                if (status == BackupConstants.TRANSPORT_OK && pmState.length() <= 0) {
                    Log.i(TAG, "Initializing (wiping) backup state and transport storage");
                    resetBackupState(mStateDir);  // Just to make sure.
                    status = mTransport.initializeDevice();
                    if (status == BackupConstants.TRANSPORT_OK) {
                        EventLog.writeEvent(BACKUP_INITIALIZE_EVENT);
@@ -1107,10 +1062,9 @@ class BackupManagerService extends IBackupManager.Stub {
                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.
                    // everything.  This must come after mBackupOrRestoreInProgress is cleared.
                    EventLog.writeEvent(BACKUP_RESET_EVENT, mTransport.transportDirName());
                    resetBackupState(mStateDir);
                    backupNow();
                }
            } catch (Exception e) {
                Log.e(TAG, "Error in backup thread", e);
@@ -1123,11 +1077,7 @@ class BackupManagerService extends IBackupManager.Stub {
                if (status != BackupConstants.TRANSPORT_OK) {
                    Log.w(TAG, "Backup pass unsuccessful, restaging");
                    for (BackupRequest req : mQueue) {
                        try {
                        dataChanged(req.appInfo.packageName);
                        } catch (RemoteException e) {
                            // can't happen; it's a local call
                        }
                    }

                    // We also want to reset the backup schedule based on whatever
@@ -1141,7 +1091,7 @@ class BackupManagerService extends IBackupManager.Stub {
                // this pass's journal any more; or it failed, in which case we just
                // re-enqueued all of these packages in the current active journal.
                // Either way, we no longer need this pass's journal.
                if (!mJournal.delete()) {
                if (mJournal != null && !mJournal.delete()) {
                    Log.e(TAG, "Unable to remove backup journal file " + mJournal);
                }

@@ -1150,6 +1100,12 @@ class BackupManagerService extends IBackupManager.Stub {
                synchronized (mQueueLock) {
                    mBackupOrRestoreInProgress = false;
                }

                if (status == BackupConstants.TRANSPORT_NOT_INITIALIZED) {
                    // This must come after mBackupOrRestoreInProgress is cleared.
                    backupNow();
                }

                mWakelock.release();
            }
        }
@@ -1782,7 +1738,7 @@ class BackupManagerService extends IBackupManager.Stub {

    // ----- IBackupManager binder interface -----

    public void dataChanged(String packageName) throws RemoteException {
    public void dataChanged(String packageName) {
        // Record that we need a backup pass for the caller.  Since multiple callers
        // may share a uid, we need to note all candidates within that uid and schedule
        // a backup pass for each of them.
@@ -1840,14 +1796,17 @@ class BackupManagerService extends IBackupManager.Stub {
    }

    private void writeToJournalLocked(String str) {
        if (mJournalStream != null) {
        RandomAccessFile out = null;
        try {
                mJournalStream.writeUTF(str);
            if (mJournal == null) mJournal = File.createTempFile("journal", null, mJournalDir);
            out = new RandomAccessFile(mJournal, "rws");
            out.seek(out.length());
            out.writeUTF(str);
        } catch (IOException e) {
                Log.e(TAG, "Error writing to backup journal");
                mJournalStream = null;
            Log.e(TAG, "Can't write " + str + " to backup journal", e);
            mJournal = null;
            }
        } finally {
            try { if (out != null) out.close(); } catch (IOException e) {}
        }
    }

@@ -1902,7 +1861,7 @@ class BackupManagerService extends IBackupManager.Stub {

    // Run a backup pass immediately for any applications that have declared
    // that they have pending updates.
    public void backupNow() throws RemoteException {
    public void backupNow() {
        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP, "backupNow");

        if (DEBUG) Log.v(TAG, "Scheduling immediate backup pass");
@@ -2210,8 +2169,16 @@ class BackupManagerService extends IBackupManager.Stub {

            pw.println("Available transports:");
            for (String t : listAllTransports()) {
                String pad = (t.equals(mCurrentTransport)) ? "  * " : "    ";
                pw.println(pad + t);
                pw.println((t.equals(mCurrentTransport) ? "  * " : "    ") + t);
                try {
                    File dir = new File(mBaseStateDir, getTransport(t).transportDirName());
                    for (File f : dir.listFiles()) {
                        pw.println("       " + f.getName() + " - " + f.length() + " state bytes");
                    }
                } catch (RemoteException e) {
                    Log.e(TAG, "Error in transportDirName()", e);
                    pw.println("        Error: " + e);
                }
            }

            pw.println("Pending init: " + mPendingInits.size());