Avoid zero-payload backups in local transport
The local debugging transport now implements
BackupTransport.checkFullBackupSize() to detect and reject backup attempts
for which no actual file content will be committed. The documentation for
checkFullBackupSize() has also been expanded to document the transport's
responsibilities in this regard.
The local transport now lazy-creates the destination file when data is
first delivered for an approved backup operation, rather than doing it
proactively in performBackup(), to ensure that changes in the datastore
are only attempted after the transport has positive confirmation that
data is indeed flowing.
Change-Id: I6e47a7e72cd938fc0ed31da4bc490540c71f9e65
diff --git a/core/java/android/app/backup/BackupTransport.java b/core/java/android/app/backup/BackupTransport.java
index 1131ff9..9540eb1 100644
--- a/core/java/android/app/backup/BackupTransport.java
+++ b/core/java/android/app/backup/BackupTransport.java
@@ -399,6 +399,13 @@
* operation will be skipped (and {@link #finishBackup() invoked} with no data for that
* package being passed to {@link #sendBackupData}.
*
+ * <p class="note">The platform does no size-based rejection of full backup attempts on
+ * its own: it is always the responsibility of the transport to implement its own policy.
+ * In particular, even if the preflighted payload size is zero, the platform will still call
+ * this method and will proceed to back up an archive metadata header with no file content
+ * if this method returns TRANSPORT_OK. To avoid storing such payloads the transport
+ * must recognize this case and return TRANSPORT_PACKAGE_REJECTED.
+ *
* Added in MNC (API 23).
*
* @param size The estimated size of the full-data payload for this app. This includes
diff --git a/core/java/com/android/internal/backup/LocalTransport.java b/core/java/com/android/internal/backup/LocalTransport.java
index e32a3a2..22d35f2 100644
--- a/core/java/com/android/internal/backup/LocalTransport.java
+++ b/core/java/com/android/internal/backup/LocalTransport.java
@@ -45,8 +45,7 @@
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
-import java.util.HashSet;
-import static android.system.OsConstants.*;
+import static android.system.OsConstants.SEEK_CUR;
/**
* Backup transport for stashing stuff into a known location on disk, and
@@ -284,8 +283,10 @@
private int tearDownFullBackup() {
if (mSocket != null) {
try {
- mFullBackupOutputStream.flush();
- mFullBackupOutputStream.close();
+ if (mFullBackupOutputStream != null) {
+ mFullBackupOutputStream.flush();
+ mFullBackupOutputStream.close();
+ }
mSocketInputStream = null;
mFullTargetPackage = null;
mSocket.close();
@@ -296,6 +297,7 @@
return TRANSPORT_ERROR;
} finally {
mSocket = null;
+ mFullBackupOutputStream = null;
}
}
return TRANSPORT_OK;
@@ -311,6 +313,18 @@
}
@Override
+ public int checkFullBackupSize(long size) {
+ // Decline zero-size "backups"
+ final int result = (size > 0) ? TRANSPORT_OK : TRANSPORT_PACKAGE_REJECTED;
+ if (result != TRANSPORT_OK) {
+ if (DEBUG) {
+ Log.v(TAG, "Declining backup of size " + size);
+ }
+ }
+ return result;
+ }
+
+ @Override
public int performFullBackup(PackageInfo targetPackage, ParcelFileDescriptor socket) {
if (mSocket != null) {
Log.e(TAG, "Attempt to initiate full backup while one is in progress");
@@ -333,22 +347,14 @@
}
mFullTargetPackage = targetPackage.packageName;
- FileOutputStream tarstream;
- try {
- File tarball = tarballFile(mFullTargetPackage);
- tarstream = new FileOutputStream(tarball);
- } catch (FileNotFoundException e) {
- return TRANSPORT_ERROR;
- }
- mFullBackupOutputStream = new BufferedOutputStream(tarstream);
mFullBackupBuffer = new byte[4096];
return TRANSPORT_OK;
}
@Override
- public int sendBackupData(int numBytes) {
- if (mFullBackupBuffer == null) {
+ public int sendBackupData(final int numBytes) {
+ if (mSocket == null) {
Log.w(TAG, "Attempted sendBackupData before performFullBackup");
return TRANSPORT_ERROR;
}
@@ -356,16 +362,29 @@
if (numBytes > mFullBackupBuffer.length) {
mFullBackupBuffer = new byte[numBytes];
}
- while (numBytes > 0) {
+
+ if (mFullBackupOutputStream == null) {
+ FileOutputStream tarstream;
try {
- int nRead = mSocketInputStream.read(mFullBackupBuffer, 0, numBytes);
+ File tarball = tarballFile(mFullTargetPackage);
+ tarstream = new FileOutputStream(tarball);
+ } catch (FileNotFoundException e) {
+ return TRANSPORT_ERROR;
+ }
+ mFullBackupOutputStream = new BufferedOutputStream(tarstream);
+ }
+
+ int bytesLeft = numBytes;
+ while (bytesLeft > 0) {
+ try {
+ int nRead = mSocketInputStream.read(mFullBackupBuffer, 0, bytesLeft);
if (nRead < 0) {
// Something went wrong if we expect data but saw EOD
Log.w(TAG, "Unexpected EOD; failing backup");
return TRANSPORT_ERROR;
}
mFullBackupOutputStream.write(mFullBackupBuffer, 0, nRead);
- numBytes -= nRead;
+ bytesLeft -= nRead;
} catch (IOException e) {
Log.e(TAG, "Error handling backup data for " + mFullTargetPackage);
return TRANSPORT_ERROR;