Fix partial-read handling during restore

...by once and for all making all of the code deal appropriately with
expected partial reads.  We also now produce a properly conformant
underlying 'tar' EOF sequence [which will be compressed to almost
nothing] to doubly bulletproof the end-of-archive logic.

Fixes bug 5133658

Change-Id: I24a785574861d64ef10fc727b9f6b235575696b0
diff --git a/services/java/com/android/server/BackupManagerService.java b/services/java/com/android/server/BackupManagerService.java
index 8b7d3a8..1c06636 100644
--- a/services/java/com/android/server/BackupManagerService.java
+++ b/services/java/com/android/server/BackupManagerService.java
@@ -2105,10 +2105,13 @@
                     backupOnePackage(pkg, out);
                 }
 
-                // Finally, shared storage if requested
+                // Shared storage if requested
                 if (mIncludeShared) {
                     backupSharedStorage();
                 }
+
+                // Done!
+                finalizeBackup(out);
             } catch (RemoteException e) {
                 Slog.e(TAG, "App died during full backup");
             } finally {
@@ -2326,6 +2329,16 @@
             }
         }
 
+        private void finalizeBackup(OutputStream out) {
+            try {
+                // A standard 'tar' EOF sequence: two 512-byte blocks of all zeroes.
+                byte[] eof = new byte[512 * 2]; // newly allocated == zero filled
+                out.write(eof);
+            } catch (IOException e) {
+                Slog.w(TAG, "Error attempting to finalize backup stream");
+            }
+        }
+
         private void writeAppManifest(PackageInfo pkg, File manifestFile, boolean withApk)
                 throws IOException {
             // Manifest format. All data are strings ending in LF:
@@ -3186,9 +3199,11 @@
         void skipTarPadding(long size, InputStream instream) throws IOException {
             long partial = (size + 512) % 512;
             if (partial > 0) {
-                byte[] buffer = new byte[512];
-                int nRead = instream.read(buffer, 0, 512 - (int)partial);
-                if (nRead >= 0) mBytes += nRead;
+                final int needed = 512 - (int)partial;
+                byte[] buffer = new byte[needed];
+                if (readExactly(instream, buffer, 0, needed) == needed) {
+                    mBytes += needed;
+                } else throw new IOException("Unexpected EOF in padding");
             }
         }
 
@@ -3199,12 +3214,11 @@
             if (info.size > 64 * 1024) {
                 throw new IOException("Restore manifest too big; corrupt? size=" + info.size);
             }
+
             byte[] buffer = new byte[(int) info.size];
-            int nRead = 0;
-            while (nRead < info.size) {
-                nRead += instream.read(buffer, nRead, (int)info.size - nRead);
-            }
-            if (nRead >= 0) mBytes += nRead;
+            if (readExactly(instream, buffer, 0, (int)info.size) == info.size) {
+                mBytes += info.size;
+            } else throw new IOException("Unexpected EOF in manifest");
 
             RestorePolicy policy = RestorePolicy.IGNORE;
             String[] str = new String[1];
@@ -3453,7 +3467,7 @@
                     }
                 } catch (IOException e) {
                     if (DEBUG) {
-                        Slog.e(TAG, "Parse error in header.  Hexdump:");
+                        Slog.e(TAG, "Parse error in header: " + e.getMessage());
                         HEXLOG(block);
                     }
                     throw e;
@@ -3479,22 +3493,31 @@
             }
         }
 
-        boolean readTarHeader(InputStream instream, byte[] block) throws IOException {
-            int totalRead = 0;
-            while (totalRead < 512) {
-                int nRead = instream.read(block, totalRead, 512 - totalRead);
-                if (nRead >= 0) {
-                    mBytes += nRead;
-                    totalRead += nRead;
-                } else {
-                    if (totalRead == 0) {
-                        // EOF instead of a new header; we're done
-                        break;
-                    }
-                    throw new IOException("Unable to read full block header, t=" + totalRead);
+        // Read exactly the given number of bytes into a buffer at the stated offset.
+        // Returns false if EOF is encountered before the requested number of bytes
+        // could be read.
+        int readExactly(InputStream in, byte[] buffer, int offset, int size)
+                throws IOException {
+            if (size <= 0) throw new IllegalArgumentException("size must be > 0");
+
+            int soFar = 0;
+            while (soFar < size) {
+                int nRead = in.read(buffer, offset + soFar, size - soFar);
+                if (nRead <= 0) {
+                    if (MORE_DEBUG) Slog.w(TAG, "- wanted exactly " + size + " but got only " + soFar);
+                    break;
                 }
+                soFar += nRead;
             }
-            return (totalRead == 512);
+            return soFar;
+        }
+
+        boolean readTarHeader(InputStream instream, byte[] block) throws IOException {
+            final int got = readExactly(instream, block, 0, 512);
+            if (got == 0) return false;     // Clean EOF
+            if (got < 512) throw new IOException("Unable to read full block header");
+            mBytes += 512;
+            return true;
         }
 
         // overwrites 'info' fields based on the pax extended header
@@ -3510,11 +3533,10 @@
             // read whole blocks, not just the content size
             int numBlocks = (int)((info.size + 511) >> 9);
             byte[] data = new byte[numBlocks * 512];
-            int nRead = instream.read(data);
-            if (nRead >= 0) mBytes += nRead;
-            if (nRead != data.length) {
-                return false;
+            if (readExactly(instream, data, 0, data.length) < data.length) {
+                throw new IOException("Unable to read full pax header");
             }
+            mBytes += data.length;
 
             final int contentSize = (int) info.size;
             int offset = 0;