Prevent (and repair) poisoned full-data backup queue
An app that transitioned from full-data to key/value backup regimes was being left in the full-data backup queue until next reboot. In edge cases this would result in the app being inappropriately shut down for backup; furthermore, it would potentially cause there to exist a full-data payload for the app that was considered "newest" and therefore be the one delivered at restore time on a new device or app (re)installation. Defense in depth: full-backup candidates are just-in-time reevaluated for validity when they come up again in the queue; app update notifications cause a reevaluation and removal from the queue if full-data is no longer the right modality; and the common engine for all cloud-facing full-data backups does an additional last-ditch validation that each stated target is actually supposed to get full-data backups rather than key/value, to backstop the checks on queue-presence validity. Bug 26744511 Change-Id: I55bea3e19a2cab0150dbe5a08dd9fc550f0068c4
This commit is contained in:
@@ -1920,7 +1920,9 @@ public class BackupManagerService {
|
||||
synchronized (mBackupParticipants) {
|
||||
if (replacing) {
|
||||
// This is the package-replaced case; we just remove the entry
|
||||
// under the old uid and fall through to re-add.
|
||||
// under the old uid and fall through to re-add. If an app
|
||||
// just added key/value backup participation, this picks it up
|
||||
// as a known participant.
|
||||
removePackageParticipantsLocked(pkgList, uid);
|
||||
}
|
||||
addPackageParticipantsLocked(pkgList);
|
||||
@@ -1933,6 +1935,14 @@ public class BackupManagerService {
|
||||
if (appGetsFullBackup(app) && appIsEligibleForBackup(app.applicationInfo)) {
|
||||
enqueueFullBackup(packageName, now);
|
||||
scheduleNextFullBackupJob(0);
|
||||
} else {
|
||||
// The app might have just transitioned out of full-data into
|
||||
// doing key/value backups, or might have just disabled backups
|
||||
// entirely. Make sure it is no longer in the full-data queue.
|
||||
synchronized (mQueueLock) {
|
||||
dequeueFullBackupLocked(packageName);
|
||||
}
|
||||
writeFullBackupScheduleAsync();
|
||||
}
|
||||
|
||||
// Transport maintenance: rebind to known existing transports that have
|
||||
@@ -1964,6 +1974,9 @@ public class BackupManagerService {
|
||||
if (replacing) {
|
||||
// The package is being updated. We'll receive a PACKAGE_ADDED shortly.
|
||||
} else {
|
||||
// Outright removal. In the full-data case, the app will be dropped
|
||||
// from the queue when its (now obsolete) name comes up again for
|
||||
// backup.
|
||||
synchronized (mBackupParticipants) {
|
||||
removePackageParticipantsLocked(pkgList, uid);
|
||||
}
|
||||
@@ -4190,11 +4203,21 @@ public class BackupManagerService {
|
||||
// as well as any explicit mention of the 'special' shared-storage agent
|
||||
// package (we handle that one at the end).
|
||||
if (MORE_DEBUG) {
|
||||
Slog.d(TAG, "Ignoring not eligible package " + pkg);
|
||||
Slog.d(TAG, "Ignoring ineligible package " + pkg);
|
||||
}
|
||||
sendBackupOnResult(mBackupObserver, pkg,
|
||||
BackupManager.ERROR_BACKUP_NOT_ALLOWED);
|
||||
continue;
|
||||
} else if (!appGetsFullBackup(info)) {
|
||||
// Cull any packages that are found in the queue but now aren't supposed
|
||||
// to get full-data backup operations.
|
||||
if (MORE_DEBUG) {
|
||||
Slog.d(TAG, "Ignoring full-data backup of key/value participant "
|
||||
+ pkg);
|
||||
}
|
||||
sendBackupOnResult(mBackupObserver, pkg,
|
||||
BackupManager.ERROR_BACKUP_NOT_ALLOWED);
|
||||
continue;
|
||||
} else if (appIsStopped(info.applicationInfo)) {
|
||||
// Cull any packages in the 'stopped' state: they've either just been
|
||||
// installed or have explicitly been force-stopped by the user. In both
|
||||
@@ -4591,22 +4614,29 @@ public class BackupManagerService {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a package from the full-data queue.
|
||||
*/
|
||||
void dequeueFullBackupLocked(String packageName) {
|
||||
final int N = mFullBackupQueue.size();
|
||||
for (int i = N-1; i >= 0; i--) {
|
||||
final FullBackupEntry e = mFullBackupQueue.get(i);
|
||||
if (packageName.equals(e.packageName)) {
|
||||
mFullBackupQueue.remove(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueue full backup for the given app, with a note about when it last ran.
|
||||
*/
|
||||
void enqueueFullBackup(String packageName, long lastBackedUp) {
|
||||
FullBackupEntry newEntry = new FullBackupEntry(packageName, lastBackedUp);
|
||||
synchronized (mQueueLock) {
|
||||
int N = mFullBackupQueue.size();
|
||||
// First, sanity check that we aren't adding a duplicate. Slow but
|
||||
// straightforward; we'll have at most on the order of a few hundred
|
||||
// items in this list.
|
||||
for (int i = N-1; i >= 0; i--) {
|
||||
final FullBackupEntry e = mFullBackupQueue.get(i);
|
||||
if (packageName.equals(e.packageName)) {
|
||||
mFullBackupQueue.remove(i);
|
||||
}
|
||||
}
|
||||
dequeueFullBackupLocked(packageName);
|
||||
|
||||
// This is also slow but easy for modest numbers of apps: work backwards
|
||||
// from the end of the queue until we find an item whose last backup
|
||||
@@ -4700,21 +4730,24 @@ public class BackupManagerService {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (mFullBackupQueue.size() == 0) {
|
||||
// no work to do so just bow out
|
||||
if (DEBUG) {
|
||||
Slog.i(TAG, "Backup queue empty; doing nothing");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// At this point we know that we have work to do, but possibly not right now.
|
||||
// At this point we think that we have work to do, but possibly not right now.
|
||||
// Any exit without actually running backups will also require that we
|
||||
// reschedule the job.
|
||||
boolean runBackup = true;
|
||||
boolean headBusy;
|
||||
|
||||
do {
|
||||
// Recheck each time, because culling due to ineligibility may
|
||||
// have emptied the queue.
|
||||
if (mFullBackupQueue.size() == 0) {
|
||||
// no work to do so just bow out
|
||||
if (DEBUG) {
|
||||
Slog.i(TAG, "Backup queue empty; doing nothing");
|
||||
}
|
||||
runBackup = false;
|
||||
break;
|
||||
}
|
||||
|
||||
headBusy = false;
|
||||
|
||||
if (!fullBackupAllowable(getTransport(mCurrentTransport))) {
|
||||
@@ -4744,6 +4777,19 @@ public class BackupManagerService {
|
||||
|
||||
try {
|
||||
PackageInfo appInfo = mPackageManager.getPackageInfo(entry.packageName, 0);
|
||||
if (!appGetsFullBackup(appInfo)) {
|
||||
// The head app isn't supposed to get full-data backups [any more];
|
||||
// so we cull it and force a loop around to consider the new head
|
||||
// app.
|
||||
if (MORE_DEBUG) {
|
||||
Slog.i(TAG, "Culling package " + entry.packageName
|
||||
+ " in full-backup queue but not eligible");
|
||||
}
|
||||
mFullBackupQueue.remove(0);
|
||||
headBusy = true; // force the while() condition
|
||||
continue;
|
||||
}
|
||||
|
||||
headBusy = mActivityManager.isAppForeground(appInfo.applicationInfo.uid);
|
||||
|
||||
if (headBusy) {
|
||||
@@ -4762,7 +4808,6 @@ public class BackupManagerService {
|
||||
enqueueFullBackup(entry.packageName,
|
||||
nextEligible - MIN_FULL_BACKUP_INTERVAL);
|
||||
}
|
||||
|
||||
} catch (NameNotFoundException nnf) {
|
||||
// So, we think we want to back this up, but it turns out the package
|
||||
// in question is no longer installed. We want to drop it from the
|
||||
|
||||
Reference in New Issue
Block a user