From 852f8e4660302a4163bfdc4a9b1e8012296522b0 Mon Sep 17 00:00:00 2001 From: Dan Egnor Date: Wed, 30 Sep 2009 11:20:45 -0700 Subject: [PATCH] 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. Don't keep open journal files -- close them after every update. A bit less efficient, but possibly more reliable (again, this is defensive programming here). Also change "rwd" to "rws" mode for fully synchronous operation. --- .../android/server/BackupManagerService.java | 325 ++++++++---------- 1 file changed, 146 insertions(+), 179 deletions(-) diff --git a/services/java/com/android/server/BackupManagerService.java b/services/java/com/android/server/BackupManagerService.java index ef0cbeeb372b4..e722eb1f9a640 100644 --- a/services/java/com/android/server/BackupManagerService.java +++ b/services/java/com/android/server/BackupManagerService.java @@ -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 mEverStoredApps = new HashSet(); // 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,56 +367,51 @@ 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 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"); - - // parse its existing contents - mEverStoredStream.seek(0); - temp.seek(0); - try { - while (true) { - PackageInfo info; - String pkg = mEverStoredStream.readUTF(); - try { - info = mPackageManager.getPackageInfo(pkg, 0); - mEverStoredApps.add(pkg); - temp.writeUTF(pkg); - if (DEBUG) Log.v(TAG, " + " + pkg); - } catch (NameNotFoundException e) { - // nope, this package was uninstalled; don't include it - if (DEBUG) Log.v(TAG, " - " + pkg); - } - } - } 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); - } - // 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; - } // 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. + // 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 = null; + RandomAccessFile in = null; + + try { + temp = new RandomAccessFile(tempProcessedFile, "rws"); + in = new RandomAccessFile(mEverStored, "r"); + + while (true) { + PackageInfo info; + String pkg = in.readUTF(); + try { + info = mPackageManager.getPackageInfo(pkg, 0); + mEverStoredApps.add(pkg); + temp.writeUTF(pkg); + if (DEBUG) Log.v(TAG, " + " + pkg); + } catch (NameNotFoundException e) { + // nope, this package was uninstalled; don't include it + if (DEBUG) Log.v(TAG, " - " + pkg); + } + } + } catch (EOFException e) { + // Once we've rewritten the backup history log, atomically replace the + // old one with the new one then reopen the file for continuing use. + if (!tempProcessedFile.renameTo(mEverStored)) { + Log.e(TAG, "Error renaming " + tempProcessedFile + " to " + mEverStored); + } + } catch (IOException e) { + 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) {} + } + } + // Register for broadcasts about package install, etc., so we can // update the provider list. IntentFilter filter = new IntentFilter(); @@ -428,41 +421,29 @@ 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(); + 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:"); + 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) { + Log.e(TAG, "Can't read " + f, e); + } finally { + // close/delete the file + try { if (in != null) in.close(); } catch (IOException e) {} + f.delete(); } } } @@ -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 participants = mBackupParticipants.valueAt(i); for (ApplicationInfo app: participants) { - try { - dataChanged(app.packageName); - } catch (RemoteException e) { - // can't happen; we're in the same process - } + dataChanged(app.packageName); } } } @@ -652,7 +618,6 @@ class BackupManagerService extends IBackupManager.Stub { // snapshot the pending-backup set and work on that ArrayList queue = new ArrayList(); - 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 - } + dataChanged(pkg.packageName); } } } @@ -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)) { - synchronized (mEverStoredApps) { - if (mEverStoredApps.add(packageName)) { - try { - mEverStoredStream.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; - } - } + if (packageName.equals(PACKAGE_MANAGER_SENTINEL)) return; + + synchronized (mEverStoredApps) { + if (!mEverStoredApps.add(packageName)) return; + + RandomAccessFile out = null; + try { + out = new RandomAccessFile(mEverStored, "rws"); + out.seek(out.length()); + out.writeUTF(packageName); + } catch (IOException e) { + 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"); - try { - mEverStoredStream.close(); - RandomAccessFile known = new RandomAccessFile(tempKnownFile, "rw"); - 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"); - } 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; - mEverStoredApps.clear(); - tempKnownFile.delete(); - mEverStored.delete(); + 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 { + known = new RandomAccessFile(tempKnownFile, "rws"); + mEverStoredApps.remove(packageName); + for (String s : mEverStoredApps) { + known.writeUTF(s); + if (DEBUG) Log.v(TAG, " " + s); } + known.close(); + 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 " + 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 - } + dataChanged(req.appInfo.packageName); } // 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) { - try { - mJournalStream.writeUTF(str); - } catch (IOException e) { - Log.e(TAG, "Error writing to backup journal"); - mJournalStream = null; - mJournal = null; - } + RandomAccessFile out = null; + try { + 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, "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());