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.
diff --git a/services/java/com/android/server/BackupManagerService.java b/services/java/com/android/server/BackupManagerService.java
index ef0cbee..e722eb1 100644
--- a/services/java/com/android/server/BackupManagerService.java
+++ b/services/java/com/android/server/BackupManagerService.java
@@ -205,11 +205,9 @@
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 @@
// 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 @@
// 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 @@
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 @@
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 @@
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
- }
+ dataChanged(app.packageName);
}
}
}
@@ -652,7 +618,6 @@
// 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 @@
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 @@
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 @@
// 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 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 @@
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 @@
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 @@
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 @@
// 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 @@
synchronized (mQueueLock) {
mBackupOrRestoreInProgress = false;
}
+
+ if (status == BackupConstants.TRANSPORT_NOT_INITIALIZED) {
+ // This must come after mBackupOrRestoreInProgress is cleared.
+ backupNow();
+ }
+
mWakelock.release();
}
}
@@ -1782,7 +1738,7 @@
// ----- 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 @@
}
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 @@
// 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 @@
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());