Merge commit '04bcf3ee761d8648d0247dd8322e3542c0319464' from
oc-mr1-dev-plus-aosp into stage-aosp-master

Change-Id: Ie321cc4ce4a2324ca7bf0f3493f23ea7999765b8
diff --git a/applypatch/Android.bp b/applypatch/Android.bp
index 922f67a..4d2d4b7 100644
--- a/applypatch/Android.bp
+++ b/applypatch/Android.bp
@@ -158,6 +158,7 @@
         "libbase",
         "libutils",
         "liblog",
+        "libbrotli",
         "libbz",
         "libz",
     ],
diff --git a/applypatch/imgdiff.cpp b/applypatch/imgdiff.cpp
index 69ad75f..f57e794 100644
--- a/applypatch/imgdiff.cpp
+++ b/applypatch/imgdiff.cpp
@@ -163,7 +163,7 @@
 #include <android-base/stringprintf.h>
 #include <android-base/strings.h>
 #include <android-base/unique_fd.h>
-#include <bsdiff.h>
+#include <bsdiff/bsdiff.h>
 #include <ziparchive/zip_archive.h>
 #include <zlib.h>
 
@@ -324,7 +324,8 @@
 }
 
 bool ImageChunk::MakePatch(const ImageChunk& tgt, const ImageChunk& src,
-                           std::vector<uint8_t>* patch_data, saidx_t** bsdiff_cache) {
+                           std::vector<uint8_t>* patch_data,
+                           bsdiff::SuffixArrayIndexInterface** bsdiff_cache) {
 #if defined(__ANDROID__)
   char ptemp[] = "/data/local/tmp/imgdiff-patch-XXXXXX";
 #else
@@ -1083,7 +1084,7 @@
   printf("Construct patches for %zu chunks...\n", tgt_image.NumOfChunks());
   patch_chunks->clear();
 
-  saidx_t* bsdiff_cache = nullptr;
+  bsdiff::SuffixArrayIndexInterface* bsdiff_cache = nullptr;
   for (size_t i = 0; i < tgt_image.NumOfChunks(); i++) {
     const auto& tgt_chunk = tgt_image[i];
 
@@ -1097,7 +1098,8 @@
                                       : src_image.FindChunkByName(tgt_chunk.GetEntryName());
 
     const auto& src_ref = (src_chunk == nullptr) ? src_image.PseudoSource() : *src_chunk;
-    saidx_t** bsdiff_cache_ptr = (src_chunk == nullptr) ? &bsdiff_cache : nullptr;
+    bsdiff::SuffixArrayIndexInterface** bsdiff_cache_ptr =
+        (src_chunk == nullptr) ? &bsdiff_cache : nullptr;
 
     std::vector<uint8_t> patch_data;
     if (!ImageChunk::MakePatch(tgt_chunk, src_ref, &patch_data, bsdiff_cache_ptr)) {
@@ -1114,7 +1116,7 @@
       patch_chunks->emplace_back(tgt_chunk, src_ref, std::move(patch_data));
     }
   }
-  free(bsdiff_cache);
+  delete bsdiff_cache;
 
   CHECK_EQ(patch_chunks->size(), tgt_image.NumOfChunks());
   return true;
diff --git a/applypatch/imgpatch.cpp b/applypatch/imgpatch.cpp
index 7a43ddb..25ba0a1 100644
--- a/applypatch/imgpatch.cpp
+++ b/applypatch/imgpatch.cpp
@@ -59,13 +59,13 @@
   int mem_level = Read4(deflate_header + 52);
   int strategy = Read4(deflate_header + 56);
 
-  std::unique_ptr<z_stream, decltype(&deflateEnd)> strm(new z_stream(), deflateEnd);
-  strm->zalloc = Z_NULL;
-  strm->zfree = Z_NULL;
-  strm->opaque = Z_NULL;
-  strm->avail_in = 0;
-  strm->next_in = nullptr;
-  int ret = deflateInit2(strm.get(), level, method, window_bits, mem_level, strategy);
+  z_stream strm;
+  strm.zalloc = Z_NULL;
+  strm.zfree = Z_NULL;
+  strm.opaque = Z_NULL;
+  strm.avail_in = 0;
+  strm.next_in = nullptr;
+  int ret = deflateInit2(&strm, level, method, window_bits, mem_level, strategy);
   if (ret != Z_OK) {
     LOG(ERROR) << "Failed to init uncompressed data deflation: " << ret;
     return false;
@@ -76,18 +76,19 @@
   size_t actual_target_length = 0;
   size_t total_written = 0;
   static constexpr size_t buffer_size = 32768;
-  auto compression_sink = [&](const uint8_t* data, size_t len) -> size_t {
+  auto compression_sink = [&strm, &actual_target_length, &expected_target_length, &total_written,
+                           &ret, &ctx, &sink](const uint8_t* data, size_t len) -> size_t {
     // The input patch length for an update never exceeds INT_MAX.
-    strm->avail_in = len;
-    strm->next_in = data;
+    strm.avail_in = len;
+    strm.next_in = data;
     do {
       std::vector<uint8_t> buffer(buffer_size);
-      strm->avail_out = buffer_size;
-      strm->next_out = buffer.data();
+      strm.avail_out = buffer_size;
+      strm.next_out = buffer.data();
       if (actual_target_length + len < expected_target_length) {
-        ret = deflate(strm.get(), Z_NO_FLUSH);
+        ret = deflate(&strm, Z_NO_FLUSH);
       } else {
-        ret = deflate(strm.get(), Z_FINISH);
+        ret = deflate(&strm, Z_FINISH);
       }
       if (ret != Z_OK && ret != Z_STREAM_END) {
         LOG(ERROR) << "Failed to deflate stream: " << ret;
@@ -95,20 +96,24 @@
         return 0;
       }
 
-      size_t have = buffer_size - strm->avail_out;
+      size_t have = buffer_size - strm.avail_out;
       total_written += have;
       if (sink(buffer.data(), have) != have) {
         LOG(ERROR) << "Failed to write " << have << " compressed bytes to output.";
         return 0;
       }
       if (ctx) SHA1_Update(ctx, buffer.data(), have);
-    } while ((strm->avail_in != 0 || strm->avail_out == 0) && ret != Z_STREAM_END);
+    } while ((strm.avail_in != 0 || strm.avail_out == 0) && ret != Z_STREAM_END);
 
     actual_target_length += len;
     return len;
   };
 
-  if (ApplyBSDiffPatch(src_data, src_len, patch, patch_offset, compression_sink, nullptr) != 0) {
+  int bspatch_result =
+      ApplyBSDiffPatch(src_data, src_len, patch, patch_offset, compression_sink, nullptr);
+  deflateEnd(&strm);
+
+  if (bspatch_result != 0) {
     return false;
   }
 
diff --git a/applypatch/include/applypatch/imgdiff_image.h b/applypatch/include/applypatch/imgdiff_image.h
index 3d29547..00a84f3 100644
--- a/applypatch/include/applypatch/imgdiff_image.h
+++ b/applypatch/include/applypatch/imgdiff_image.h
@@ -24,7 +24,7 @@
 #include <string>
 #include <vector>
 
-#include <bsdiff.h>
+#include <bsdiff/bsdiff.h>
 #include <ziparchive/zip_archive.h>
 #include <zlib.h>
 
@@ -98,7 +98,8 @@
    * repeatedly, pass nullptr if not needed.
    */
   static bool MakePatch(const ImageChunk& tgt, const ImageChunk& src,
-                        std::vector<uint8_t>* patch_data, saidx_t** bsdiff_cache);
+                        std::vector<uint8_t>* patch_data,
+                        bsdiff::SuffixArrayIndexInterface** bsdiff_cache);
 
  private:
   const uint8_t* GetRawData() const;
diff --git a/minui/Android.mk b/minui/Android.mk
index 546ba2f..ae1552b 100644
--- a/minui/Android.mk
+++ b/minui/Android.mk
@@ -28,7 +28,7 @@
 
 LOCAL_WHOLE_STATIC_LIBRARIES := \
     libadf \
-    libdrm_platform \
+    libdrm \
     libsync_recovery
 
 LOCAL_STATIC_LIBRARIES := \
diff --git a/tests/Android.mk b/tests/Android.mk
index b0f71a8..8ebb603 100644
--- a/tests/Android.mk
+++ b/tests/Android.mk
@@ -208,6 +208,7 @@
     libutils \
     libbase \
     libcrypto \
+    libbrotli \
     libbz \
     libdivsufsort64 \
     libdivsufsort \
diff --git a/tests/common/test_constants.h b/tests/common/test_constants.h
index f6b6922..514818e 100644
--- a/tests/common/test_constants.h
+++ b/tests/common/test_constants.h
@@ -19,6 +19,8 @@
 
 #include <stdlib.h>
 
+#include <string>
+
 // Zip entries in ziptest_valid.zip.
 static const std::string kATxtContents("abcdefghabcdefgh\n");
 static const std::string kBTxtContents("abcdefgh\n");
@@ -30,10 +32,14 @@
 // echo -n -e "abcdefgh\n" | sha1sum
 static const std::string kBTxtSha1Sum("e414af7161c9554089f4106d6f1797ef14a73666");
 
-static const char* data_root = getenv("ANDROID_DATA");
-
 static std::string from_testdata_base(const std::string& fname) {
-  return std::string(data_root) + "/nativetest/recovery/testdata/" + fname;
+#ifdef __ANDROID__
+  static std::string data_root = getenv("ANDROID_DATA");
+#else
+  static std::string data_root = std::string(getenv("ANDROID_PRODUCT_OUT")) + "/data";
+#endif
+
+  return data_root + "/nativetest/recovery/testdata/" + fname;
 }
 
 #endif  // _OTA_TEST_CONSTANTS_H
diff --git a/tests/component/imgdiff_test.cpp b/tests/component/imgdiff_test.cpp
index bf591da..6de804e 100644
--- a/tests/component/imgdiff_test.cpp
+++ b/tests/component/imgdiff_test.cpp
@@ -32,6 +32,8 @@
 #include <gtest/gtest.h>
 #include <ziparchive/zip_writer.h>
 
+#include "common/test_constants.h"
+
 using android::base::get_unaligned;
 
 // Sanity check for the given imgdiff patch header.
@@ -148,7 +150,7 @@
 TEST(ImgdiffTest, zip_mode_smoke_store) {
   // Construct src and tgt zip files.
   TemporaryFile src_file;
-  FILE* src_file_ptr = fdopen(src_file.fd, "wb");
+  FILE* src_file_ptr = fdopen(src_file.release(), "wb");
   ZipWriter src_writer(src_file_ptr);
   ASSERT_EQ(0, src_writer.StartEntry("file1.txt", 0));  // Store mode.
   const std::string src_content("abcdefg");
@@ -158,7 +160,7 @@
   ASSERT_EQ(0, fclose(src_file_ptr));
 
   TemporaryFile tgt_file;
-  FILE* tgt_file_ptr = fdopen(tgt_file.fd, "wb");
+  FILE* tgt_file_ptr = fdopen(tgt_file.release(), "wb");
   ZipWriter tgt_writer(tgt_file_ptr);
   ASSERT_EQ(0, tgt_writer.StartEntry("file1.txt", 0));  // Store mode.
   const std::string tgt_content("abcdefgxyz");
@@ -197,7 +199,7 @@
 TEST(ImgdiffTest, zip_mode_smoke_compressed) {
   // Construct src and tgt zip files.
   TemporaryFile src_file;
-  FILE* src_file_ptr = fdopen(src_file.fd, "wb");
+  FILE* src_file_ptr = fdopen(src_file.release(), "wb");
   ZipWriter src_writer(src_file_ptr);
   ASSERT_EQ(0, src_writer.StartEntry("file1.txt", ZipWriter::kCompress));
   const std::string src_content("abcdefg");
@@ -207,7 +209,7 @@
   ASSERT_EQ(0, fclose(src_file_ptr));
 
   TemporaryFile tgt_file;
-  FILE* tgt_file_ptr = fdopen(tgt_file.fd, "wb");
+  FILE* tgt_file_ptr = fdopen(tgt_file.release(), "wb");
   ZipWriter tgt_writer(tgt_file_ptr);
   ASSERT_EQ(0, tgt_writer.StartEntry("file1.txt", ZipWriter::kCompress));
   const std::string tgt_content("abcdefgxyz");
@@ -246,7 +248,7 @@
 TEST(ImgdiffTest, zip_mode_smoke_trailer_zeros) {
   // Construct src and tgt zip files.
   TemporaryFile src_file;
-  FILE* src_file_ptr = fdopen(src_file.fd, "wb");
+  FILE* src_file_ptr = fdopen(src_file.release(), "wb");
   ZipWriter src_writer(src_file_ptr);
   ASSERT_EQ(0, src_writer.StartEntry("file1.txt", ZipWriter::kCompress));
   const std::string src_content("abcdefg");
@@ -256,7 +258,7 @@
   ASSERT_EQ(0, fclose(src_file_ptr));
 
   TemporaryFile tgt_file;
-  FILE* tgt_file_ptr = fdopen(tgt_file.fd, "wb");
+  FILE* tgt_file_ptr = fdopen(tgt_file.release(), "wb");
   ZipWriter tgt_writer(tgt_file_ptr);
   ASSERT_EQ(0, tgt_writer.StartEntry("file1.txt", ZipWriter::kCompress));
   const std::string tgt_content("abcdefgxyz");
@@ -761,7 +763,7 @@
   //  3 blocks  'a'    12 blocks 'd' (exceeds limit)
   //                   3 blocks  'e'
   TemporaryFile tgt_file;
-  FILE* tgt_file_ptr = fdopen(tgt_file.fd, "wb");
+  FILE* tgt_file_ptr = fdopen(tgt_file.release(), "wb");
   ZipWriter tgt_writer(tgt_file_ptr);
   construct_store_entry(
       { { "a", 3, 'a' }, { "b", 3, 'b' }, { "c", 8, 'c' }, { "d", 12, 'd' }, { "e", 3, 'e' } },
@@ -770,7 +772,7 @@
   ASSERT_EQ(0, fclose(tgt_file_ptr));
 
   TemporaryFile src_file;
-  FILE* src_file_ptr = fdopen(src_file.fd, "wb");
+  FILE* src_file_ptr = fdopen(src_file.release(), "wb");
   ZipWriter src_writer(src_file_ptr);
   construct_store_entry({ { "d", 12, 'd' }, { "c", 8, 'c' }, { "b", 3, 'b' }, { "a", 3, 'a' } },
                         &src_writer);
@@ -792,49 +794,26 @@
   std::string tgt;
   ASSERT_TRUE(android::base::ReadFileToString(tgt_file.path, &tgt));
 
-  // Expect 4 pieces of patch.(Rougly 3'a',3'b'; 8'c'; 10'd'; 2'd'3'e')
+  // Expect 4 pieces of patch. (Roughly 3'a',3'b'; 8'c'; 10'd'; 2'd'3'e')
   GenerateAndCheckSplitTarget(debug_dir.path, 4, tgt);
 }
 
 TEST(ImgdiffTest, zip_mode_deflate_large_apk) {
-  // Generate 50 blocks of random data.
-  std::string random_data;
-  random_data.reserve(4096 * 50);
-  generate_n(back_inserter(random_data), 4096 * 50, []() { return rand() % 256; });
-
-  // Construct src and tgt zip files with limit = 10 blocks.
+  // Src and tgt zip files are constructed as follows.
   //     src               tgt
   //  22 blocks, "d"    4  blocks,  "a"
   //  5 blocks,  "b"    4  blocks,  "b"
-  //  3 blocks,  "a"    7  blocks,  "c" (exceeds limit)
-  //  8 blocks,  "c"    20 blocks,  "d" (exceeds limit)
-  //  1 block,   "f"    2  blocks,  "e"
-  TemporaryFile tgt_file;
-  FILE* tgt_file_ptr = fdopen(tgt_file.fd, "wb");
-  ZipWriter tgt_writer(tgt_file_ptr);
-
-  construct_deflate_entry(
-      { { "a", 0, 4 }, { "b", 5, 4 }, { "c", 12, 8 }, { "d", 21, 20 }, { "e", 45, 2 },
-        { "f", 48, 1 } }, &tgt_writer, random_data);
-
-  ASSERT_EQ(0, tgt_writer.Finish());
-  ASSERT_EQ(0, fclose(tgt_file_ptr));
-
-  TemporaryFile src_file;
-  FILE* src_file_ptr = fdopen(src_file.fd, "wb");
-  ZipWriter src_writer(src_file_ptr);
-
-  construct_deflate_entry(
-      { { "d", 21, 22 }, { "b", 5, 5 }, { "a", 0, 3 }, { "g", 9, 1 }, { "c", 11, 8 },
-        { "f", 45, 1 } }, &src_writer, random_data);
-
-  ASSERT_EQ(0, src_writer.Finish());
-  ASSERT_EQ(0, fclose(src_file_ptr));
+  //  3 blocks,  "a"    8  blocks,  "c" (exceeds limit)
+  //  1 block,   "g"    20 blocks,  "d" (exceeds limit)
+  //  8 blocks,  "c"    2  blocks,  "e"
+  //  1 block,   "f"    1  block ,  "f"
+  std::string tgt_path = from_testdata_base("deflate_tgt.zip");
+  std::string src_path = from_testdata_base("deflate_src.zip");
 
   ZipModeImage src_image(true, 10 * 4096);
   ZipModeImage tgt_image(false, 10 * 4096);
-  ASSERT_TRUE(src_image.Initialize(src_file.path));
-  ASSERT_TRUE(tgt_image.Initialize(tgt_file.path));
+  ASSERT_TRUE(src_image.Initialize(src_path));
+  ASSERT_TRUE(tgt_image.Initialize(tgt_path));
   ASSERT_TRUE(ZipModeImage::CheckAndProcessChunks(&tgt_image, &src_image));
 
   src_image.DumpChunks();
@@ -846,11 +825,12 @@
   ZipModeImage::SplitZipModeImageWithLimit(tgt_image, src_image, &split_tgt_images,
                                            &split_src_images, &split_src_ranges);
 
-  // src_piece 1: a 3 blocks, b 5 blocks
-  // src_piece 2: c 8 blocks
-  // src_piece 3: d-0 10 block
-  // src_piece 4: d-1 10 blocks
-  // src_piece 5: e 1 block, CD
+  // Expected split images with limit = 10 blocks.
+  // src_piece 0: a 3 blocks, b 5 blocks
+  // src_piece 1: c 8 blocks
+  // src_piece 2: d-0 10 block
+  // src_piece 3: d-1 10 blocks
+  // src_piece 4: e 1 block, CD
   ASSERT_EQ(split_tgt_images.size(), split_src_images.size());
   ASSERT_EQ(static_cast<size_t>(5), split_tgt_images.size());
 
@@ -883,24 +863,21 @@
   ASSERT_EQ("2", android::base::Trim(info_list[0]));
   ASSERT_EQ("5", android::base::Trim(info_list[1]));
 
-  std::vector<size_t> patch_size;
+  std::string tgt;
+  ASSERT_TRUE(android::base::ReadFileToString(tgt_path, &tgt));
+  ASSERT_EQ(static_cast<size_t>(160385), tgt.size());
+  std::vector<std::string> tgt_file_ranges = {
+    "36864 2,22,31", "32768 2,31,40", "40960 2,0,11", "40960 2,11,21", "8833 4,21,22,40,41",
+  };
+
   for (size_t i = 0; i < 5; i++) {
-    struct stat st = {};
+    struct stat st;
     std::string path = android::base::StringPrintf("%s/patch-%zu", debug_dir.path, i);
     ASSERT_EQ(0, stat(path.c_str(), &st));
-    patch_size.push_back(st.st_size);
+    ASSERT_EQ(std::to_string(st.st_size) + " " + tgt_file_ranges[i],
+              android::base::Trim(info_list[i + 2]));
   }
 
-  ASSERT_EQ(std::to_string(patch_size[0]) + " 36864 2,22,31", android::base::Trim(info_list[2]));
-  ASSERT_EQ(std::to_string(patch_size[1]) + " 32768 2,31,40", android::base::Trim(info_list[3]));
-  ASSERT_EQ(std::to_string(patch_size[2]) + " 40960 2,0,11", android::base::Trim(info_list[4]));
-  ASSERT_EQ(std::to_string(patch_size[3]) + " 40960 2,11,21", android::base::Trim(info_list[5]));
-  ASSERT_EQ(std::to_string(patch_size[4]) + " 8833 4,21,22,40,41",
-            android::base::Trim(info_list[6]));
-
-  std::string tgt;
-  ASSERT_TRUE(android::base::ReadFileToString(tgt_file.path, &tgt));
-
   GenerateAndCheckSplitTarget(debug_dir.path, 5, tgt);
 }
 
@@ -911,7 +888,7 @@
   generate_n(back_inserter(random_data), 4096 * 20, []() { return rand() % 256; });
 
   TemporaryFile tgt_file;
-  FILE* tgt_file_ptr = fdopen(tgt_file.fd, "wb");
+  FILE* tgt_file_ptr = fdopen(tgt_file.release(), "wb");
   ZipWriter tgt_writer(tgt_file_ptr);
 
   construct_deflate_entry({ { "a", 0, 4 }, { "b", 5, 5 }, { "c", 11, 5 } }, &tgt_writer,
@@ -922,7 +899,7 @@
 
   // We don't have a matching source entry.
   TemporaryFile src_file;
-  FILE* src_file_ptr = fdopen(src_file.fd, "wb");
+  FILE* src_file_ptr = fdopen(src_file.release(), "wb");
   ZipWriter src_writer(src_file_ptr);
   construct_store_entry({ { "d", 1, 'd' } }, &src_writer);
   ASSERT_EQ(0, src_writer.Finish());
@@ -954,7 +931,7 @@
   generate_n(back_inserter(random_data), 4096 * 20, []() { return rand() % 256; });
 
   TemporaryFile tgt_file;
-  FILE* tgt_file_ptr = fdopen(tgt_file.fd, "wb");
+  FILE* tgt_file_ptr = fdopen(tgt_file.release(), "wb");
   ZipWriter tgt_writer(tgt_file_ptr);
 
   construct_deflate_entry({ { "a", 0, 10 }, { "b", 10, 5 } }, &tgt_writer, random_data);
@@ -964,7 +941,7 @@
 
   // Construct 10 blocks of source.
   TemporaryFile src_file;
-  FILE* src_file_ptr = fdopen(src_file.fd, "wb");
+  FILE* src_file_ptr = fdopen(src_file.release(), "wb");
   ZipWriter src_writer(src_file_ptr);
   construct_deflate_entry({ { "a", 1, 10 } }, &src_writer, random_data);
   ASSERT_EQ(0, src_writer.Finish());
@@ -985,6 +962,6 @@
   std::string tgt;
   ASSERT_TRUE(android::base::ReadFileToString(tgt_file.path, &tgt));
 
-  // Expect 1 pieces of patch since limit is larger than the zip file size.
+  // Expect 1 piece of patch since limit is larger than the zip file size.
   GenerateAndCheckSplitTarget(debug_dir.path, 1, tgt);
 }
diff --git a/tests/component/install_test.cpp b/tests/component/install_test.cpp
index 7bb4960..d19d788 100644
--- a/tests/component/install_test.cpp
+++ b/tests/component/install_test.cpp
@@ -37,7 +37,7 @@
 
 TEST(InstallTest, verify_package_compatibility_no_entry) {
   TemporaryFile temp_file;
-  FILE* zip_file = fdopen(temp_file.fd, "w");
+  FILE* zip_file = fdopen(temp_file.release(), "w");
   ZipWriter writer(zip_file);
   // The archive must have something to be opened correctly.
   ASSERT_EQ(0, writer.StartEntry("dummy_entry", 0));
@@ -54,7 +54,7 @@
 
 TEST(InstallTest, verify_package_compatibility_invalid_entry) {
   TemporaryFile temp_file;
-  FILE* zip_file = fdopen(temp_file.fd, "w");
+  FILE* zip_file = fdopen(temp_file.release(), "w");
   ZipWriter writer(zip_file);
   ASSERT_EQ(0, writer.StartEntry("compatibility.zip", 0));
   ASSERT_EQ(0, writer.FinishEntry());
@@ -70,7 +70,7 @@
 
 TEST(InstallTest, read_metadata_from_package_smoke) {
   TemporaryFile temp_file;
-  FILE* zip_file = fdopen(temp_file.fd, "w");
+  FILE* zip_file = fdopen(temp_file.release(), "w");
   ZipWriter writer(zip_file);
   ASSERT_EQ(0, writer.StartEntry("META-INF/com/android/metadata", kCompressStored));
   const std::string content("abcdefg");
@@ -87,7 +87,7 @@
   CloseArchive(zip);
 
   TemporaryFile temp_file2;
-  FILE* zip_file2 = fdopen(temp_file2.fd, "w");
+  FILE* zip_file2 = fdopen(temp_file2.release(), "w");
   ZipWriter writer2(zip_file2);
   ASSERT_EQ(0, writer2.StartEntry("META-INF/com/android/metadata", kCompressDeflated));
   ASSERT_EQ(0, writer2.WriteBytes(content.data(), content.size()));
@@ -104,7 +104,7 @@
 
 TEST(InstallTest, read_metadata_from_package_no_entry) {
   TemporaryFile temp_file;
-  FILE* zip_file = fdopen(temp_file.fd, "w");
+  FILE* zip_file = fdopen(temp_file.release(), "w");
   ZipWriter writer(zip_file);
   ASSERT_EQ(0, writer.StartEntry("dummy_entry", kCompressStored));
   ASSERT_EQ(0, writer.FinishEntry());
@@ -120,7 +120,7 @@
 
 TEST(InstallTest, verify_package_compatibility_with_libvintf_malformed_xml) {
   TemporaryFile compatibility_zip_file;
-  FILE* compatibility_zip = fdopen(compatibility_zip_file.fd, "w");
+  FILE* compatibility_zip = fdopen(compatibility_zip_file.release(), "w");
   ZipWriter compatibility_zip_writer(compatibility_zip);
   ASSERT_EQ(0, compatibility_zip_writer.StartEntry("system_manifest.xml", kCompressDeflated));
   std::string malformed_xml = "malformed";
@@ -130,7 +130,7 @@
   ASSERT_EQ(0, fclose(compatibility_zip));
 
   TemporaryFile temp_file;
-  FILE* zip_file = fdopen(temp_file.fd, "w");
+  FILE* zip_file = fdopen(temp_file.release(), "w");
   ZipWriter writer(zip_file);
   ASSERT_EQ(0, writer.StartEntry("compatibility.zip", kCompressStored));
   std::string compatibility_zip_content;
@@ -165,7 +165,7 @@
   ASSERT_TRUE(
       android::base::ReadFileToString(system_manifest_xml_path, &system_manifest_xml_content));
   TemporaryFile compatibility_zip_file;
-  FILE* compatibility_zip = fdopen(compatibility_zip_file.fd, "w");
+  FILE* compatibility_zip = fdopen(compatibility_zip_file.release(), "w");
   ZipWriter compatibility_zip_writer(compatibility_zip);
   ASSERT_EQ(0, compatibility_zip_writer.StartEntry("system_manifest.xml", kCompressDeflated));
   ASSERT_EQ(0, compatibility_zip_writer.WriteBytes(system_manifest_xml_content.data(),
@@ -175,7 +175,7 @@
   ASSERT_EQ(0, fclose(compatibility_zip));
 
   TemporaryFile temp_file;
-  FILE* zip_file = fdopen(temp_file.fd, "w");
+  FILE* zip_file = fdopen(temp_file.release(), "w");
   ZipWriter writer(zip_file);
   ASSERT_EQ(0, writer.StartEntry("compatibility.zip", kCompressStored));
   std::string compatibility_zip_content;
@@ -202,7 +202,7 @@
 #ifdef AB_OTA_UPDATER
 static void VerifyAbUpdateBinaryCommand(const std::string& serialno, bool success = true) {
   TemporaryFile temp_file;
-  FILE* zip_file = fdopen(temp_file.fd, "w");
+  FILE* zip_file = fdopen(temp_file.release(), "w");
   ZipWriter writer(zip_file);
   ASSERT_EQ(0, writer.StartEntry("payload.bin", kCompressStored));
   ASSERT_EQ(0, writer.FinishEntry());
@@ -258,7 +258,7 @@
   VerifyAbUpdateBinaryCommand({});
 #else
   TemporaryFile temp_file;
-  FILE* zip_file = fdopen(temp_file.fd, "w");
+  FILE* zip_file = fdopen(temp_file.release(), "w");
   ZipWriter writer(zip_file);
   static constexpr const char* UPDATE_BINARY_NAME = "META-INF/com/google/android/update-binary";
   ASSERT_EQ(0, writer.StartEntry(UPDATE_BINARY_NAME, kCompressStored));
@@ -303,7 +303,7 @@
 TEST(InstallTest, update_binary_command_invalid) {
 #ifdef AB_OTA_UPDATER
   TemporaryFile temp_file;
-  FILE* zip_file = fdopen(temp_file.fd, "w");
+  FILE* zip_file = fdopen(temp_file.release(), "w");
   ZipWriter writer(zip_file);
   // Missing payload_properties.txt.
   ASSERT_EQ(0, writer.StartEntry("payload.bin", kCompressStored));
@@ -334,7 +334,7 @@
   CloseArchive(zip);
 #else
   TemporaryFile temp_file;
-  FILE* zip_file = fdopen(temp_file.fd, "w");
+  FILE* zip_file = fdopen(temp_file.release(), "w");
   ZipWriter writer(zip_file);
   // The archive must have something to be opened correctly.
   ASSERT_EQ(0, writer.StartEntry("dummy_entry", 0));
diff --git a/tests/component/updater_test.cpp b/tests/component/updater_test.cpp
index e6aec4a..d9d01d4 100644
--- a/tests/component/updater_test.cpp
+++ b/tests/component/updater_test.cpp
@@ -23,6 +23,7 @@
 #include <algorithm>
 #include <memory>
 #include <string>
+#include <unordered_map>
 #include <vector>
 
 #include <android-base/file.h>
@@ -32,7 +33,7 @@
 #include <android-base/test_utils.h>
 #include <bootloader_message/bootloader_message.h>
 #include <brotli/encode.h>
-#include <bsdiff.h>
+#include <bsdiff/bsdiff.h>
 #include <gtest/gtest.h>
 #include <ziparchive/zip_archive.h>
 #include <ziparchive/zip_writer.h>
@@ -74,6 +75,23 @@
   ASSERT_EQ(cause_code, state.cause_code);
 }
 
+static void BuildUpdatePackage(const std::unordered_map<std::string, std::string>& entries,
+                               int fd) {
+  FILE* zip_file_ptr = fdopen(fd, "wb");
+  ZipWriter zip_writer(zip_file_ptr);
+
+  for (const auto& entry : entries) {
+    ASSERT_EQ(0, zip_writer.StartEntry(entry.first.c_str(), 0));
+    if (!entry.second.empty()) {
+      ASSERT_EQ(0, zip_writer.WriteBytes(entry.second.data(), entry.second.size()));
+    }
+    ASSERT_EQ(0, zip_writer.FinishEntry());
+  }
+
+  ASSERT_EQ(0, zip_writer.Finish());
+  ASSERT_EQ(0, fclose(zip_file_ptr));
+}
+
 static std::string get_sha1(const std::string& content) {
   uint8_t digest[SHA_DIGEST_LENGTH];
   SHA1(reinterpret_cast<const uint8_t*>(content.c_str()), content.size(), digest);
@@ -420,30 +438,19 @@
   ASSERT_EQ(0, fclose(updater_info.cmd_pipe));
 }
 
-TEST_F(UpdaterTest, block_image_update) {
-  // Create a zip file with new_data and patch_data.
-  TemporaryFile zip_file;
-  FILE* zip_file_ptr = fdopen(zip_file.release(), "wb");
-  ZipWriter zip_writer(zip_file_ptr);
-
-  // Add a dummy new data.
-  ASSERT_EQ(0, zip_writer.StartEntry("new_data", 0));
-  ASSERT_EQ(0, zip_writer.FinishEntry());
-
-  // Generate and add the patch data.
+TEST_F(UpdaterTest, block_image_update_patch_data) {
   std::string src_content = std::string(4096, 'a') + std::string(4096, 'c');
   std::string tgt_content = std::string(4096, 'b') + std::string(4096, 'd');
+
+  // Generate the patch data.
   TemporaryFile patch_file;
   ASSERT_EQ(0, bsdiff::bsdiff(reinterpret_cast<const uint8_t*>(src_content.data()),
       src_content.size(), reinterpret_cast<const uint8_t*>(tgt_content.data()),
       tgt_content.size(), patch_file.path, nullptr));
   std::string patch_content;
   ASSERT_TRUE(android::base::ReadFileToString(patch_file.path, &patch_content));
-  ASSERT_EQ(0, zip_writer.StartEntry("patch_data", 0));
-  ASSERT_EQ(0, zip_writer.WriteBytes(patch_content.data(), patch_content.size()));
-  ASSERT_EQ(0, zip_writer.FinishEntry());
 
-  // Add two transfer lists. The first one contains a bsdiff; and we expect the update to succeed.
+  // Create the transfer list that contains a bsdiff.
   std::string src_hash = get_sha1(src_content);
   std::string tgt_hash = get_sha1(tgt_content);
   std::vector<std::string> transfer_list = {
@@ -456,27 +463,16 @@
                                 src_hash.c_str(), tgt_hash.c_str(), src_hash.c_str()),
     "free " + src_hash,
   };
-  ASSERT_EQ(0, zip_writer.StartEntry("transfer_list", 0));
-  std::string commands = android::base::Join(transfer_list, '\n');
-  ASSERT_EQ(0, zip_writer.WriteBytes(commands.data(), commands.size()));
-  ASSERT_EQ(0, zip_writer.FinishEntry());
 
-  // Stash and free some blocks, then fail the 2nd update intentionally.
-  std::vector<std::string> fail_transfer_list = {
-    "4",
-    "2",
-    "0",
-    "2",
-    "stash " + tgt_hash + " 2,0,2",
-    "free " + tgt_hash,
-    "fail",
+  std::unordered_map<std::string, std::string> entries = {
+    { "new_data", "" },
+    { "patch_data", patch_content },
+    { "transfer_list", android::base::Join(transfer_list, '\n') },
   };
-  ASSERT_EQ(0, zip_writer.StartEntry("fail_transfer_list", 0));
-  std::string fail_commands = android::base::Join(fail_transfer_list, '\n');
-  ASSERT_EQ(0, zip_writer.WriteBytes(fail_commands.data(), fail_commands.size()));
-  ASSERT_EQ(0, zip_writer.FinishEntry());
-  ASSERT_EQ(0, zip_writer.Finish());
-  ASSERT_EQ(0, fclose(zip_file_ptr));
+
+  // Build the update package.
+  TemporaryFile zip_file;
+  BuildUpdatePackage(entries, zip_file.release());
 
   MemMapping map;
   ASSERT_TRUE(map.MapFile(zip_file.path));
@@ -491,7 +487,7 @@
   updater_info.package_zip_addr = map.addr;
   updater_info.package_zip_len = map.length;
 
-  // Execute the commands in the 1st transfer list.
+  // Execute the commands in the transfer list.
   TemporaryFile update_file;
   ASSERT_TRUE(android::base::WriteStringToFile(src_content, update_file.path));
   std::string script = "block_image_update(\"" + std::string(update_file.path) +
@@ -502,44 +498,98 @@
   ASSERT_TRUE(android::base::ReadFileToString(update_file.path, &updated_content));
   ASSERT_EQ(tgt_hash, get_sha1(updated_content));
 
-  // Expect the 2nd update to fail, but expect the stashed blocks to be freed.
-  script = "block_image_update(\"" + std::string(update_file.path) +
-      R"(", package_extract_file("fail_transfer_list"), "new_data", "patch_data"))";
+  ASSERT_EQ(0, fclose(updater_info.cmd_pipe));
+  CloseArchive(handle);
+}
+
+TEST_F(UpdaterTest, block_image_update_fail) {
+  std::string src_content(4096 * 2, 'e');
+  std::string src_hash = get_sha1(src_content);
+  // Stash and free some blocks, then fail the update intentionally.
+  std::vector<std::string> transfer_list = {
+    "4", "2", "0", "2", "stash " + src_hash + " 2,0,2", "free " + src_hash, "fail",
+  };
+
+  // Add a new data of 10 bytes to test the deadlock.
+  std::unordered_map<std::string, std::string> entries = {
+    { "new_data", std::string(10, 0) },
+    { "patch_data", "" },
+    { "transfer_list", android::base::Join(transfer_list, '\n') },
+  };
+
+  // Build the update package.
+  TemporaryFile zip_file;
+  BuildUpdatePackage(entries, zip_file.release());
+
+  MemMapping map;
+  ASSERT_TRUE(map.MapFile(zip_file.path));
+  ZipArchiveHandle handle;
+  ASSERT_EQ(0, OpenArchiveFromMemory(map.addr, map.length, zip_file.path, &handle));
+
+  // Set up the handler, command_pipe, patch offset & length.
+  UpdaterInfo updater_info;
+  updater_info.package_zip = handle;
+  TemporaryFile temp_pipe;
+  updater_info.cmd_pipe = fdopen(temp_pipe.release(), "wbe");
+  updater_info.package_zip_addr = map.addr;
+  updater_info.package_zip_len = map.length;
+
+  TemporaryFile update_file;
+  ASSERT_TRUE(android::base::WriteStringToFile(src_content, update_file.path));
+  // Expect the stashed blocks to be freed.
+  std::string script = "block_image_update(\"" + std::string(update_file.path) +
+                       R"(", package_extract_file("transfer_list"), "new_data", "patch_data"))";
   expect("", script.c_str(), kNoCause, &updater_info);
   // Updater generates the stash name based on the input file name.
   std::string name_digest = get_sha1(update_file.path);
   std::string stash_base = "/cache/recovery/" + name_digest;
   ASSERT_EQ(0, access(stash_base.c_str(), F_OK));
-  ASSERT_EQ(-1, access((stash_base + tgt_hash).c_str(), F_OK));
+  ASSERT_EQ(-1, access((stash_base + src_hash).c_str(), F_OK));
   ASSERT_EQ(0, rmdir(stash_base.c_str()));
 
   ASSERT_EQ(0, fclose(updater_info.cmd_pipe));
   CloseArchive(handle);
 }
 
-TEST_F(UpdaterTest, new_data_short_write) {
-  // Create a zip file with new_data.
+TEST_F(UpdaterTest, new_data_over_write) {
+  std::vector<std::string> transfer_list = {
+    "4", "1", "0", "0", "new 2,0,1",
+  };
+
+  // Write 4096 + 100 bytes of new data.
+  std::unordered_map<std::string, std::string> entries = {
+    { "new_data", std::string(4196, 0) },
+    { "patch_data", "" },
+    { "transfer_list", android::base::Join(transfer_list, '\n') },
+  };
+
+  // Build the update package.
   TemporaryFile zip_file;
-  FILE* zip_file_ptr = fdopen(zip_file.release(), "wb");
-  ZipWriter zip_writer(zip_file_ptr);
+  BuildUpdatePackage(entries, zip_file.release());
 
-  // Add the empty new data.
-  ASSERT_EQ(0, zip_writer.StartEntry("empty_new_data", 0));
-  ASSERT_EQ(0, zip_writer.FinishEntry());
-  // Add the short written new data.
-  ASSERT_EQ(0, zip_writer.StartEntry("short_new_data", 0));
-  std::string new_data_short = std::string(10, 'a');
-  ASSERT_EQ(0, zip_writer.WriteBytes(new_data_short.data(), new_data_short.size()));
-  ASSERT_EQ(0, zip_writer.FinishEntry());
-  // Add the data of exactly one block.
-  ASSERT_EQ(0, zip_writer.StartEntry("exact_new_data", 0));
-  std::string new_data_exact = std::string(4096, 'a');
-  ASSERT_EQ(0, zip_writer.WriteBytes(new_data_exact.data(), new_data_exact.size()));
-  ASSERT_EQ(0, zip_writer.FinishEntry());
-  // Add a dummy patch data.
-  ASSERT_EQ(0, zip_writer.StartEntry("patch_data", 0));
-  ASSERT_EQ(0, zip_writer.FinishEntry());
+  MemMapping map;
+  ASSERT_TRUE(map.MapFile(zip_file.path));
+  ZipArchiveHandle handle;
+  ASSERT_EQ(0, OpenArchiveFromMemory(map.addr, map.length, zip_file.path, &handle));
 
+  // Set up the handler, command_pipe, patch offset & length.
+  UpdaterInfo updater_info;
+  updater_info.package_zip = handle;
+  TemporaryFile temp_pipe;
+  updater_info.cmd_pipe = fdopen(temp_pipe.release(), "wbe");
+  updater_info.package_zip_addr = map.addr;
+  updater_info.package_zip_len = map.length;
+
+  TemporaryFile update_file;
+  std::string script = "block_image_update(\"" + std::string(update_file.path) +
+                       R"(", package_extract_file("transfer_list"), "new_data", "patch_data"))";
+  expect("t", script.c_str(), kNoCause, &updater_info);
+
+  ASSERT_EQ(0, fclose(updater_info.cmd_pipe));
+  CloseArchive(handle);
+}
+
+TEST_F(UpdaterTest, new_data_short_write) {
   std::vector<std::string> transfer_list = {
     "4",
     "1",
@@ -547,12 +597,17 @@
     "0",
     "new 2,0,1",
   };
-  ASSERT_EQ(0, zip_writer.StartEntry("transfer_list", 0));
-  std::string commands = android::base::Join(transfer_list, '\n');
-  ASSERT_EQ(0, zip_writer.WriteBytes(commands.data(), commands.size()));
-  ASSERT_EQ(0, zip_writer.FinishEntry());
-  ASSERT_EQ(0, zip_writer.Finish());
-  ASSERT_EQ(0, fclose(zip_file_ptr));
+
+  std::unordered_map<std::string, std::string> entries = {
+    { "empty_new_data", "" },
+    { "short_new_data", std::string(10, 'a') },
+    { "exact_new_data", std::string(4096, 'a') },
+    { "patch_data", "" },
+    { "transfer_list", android::base::Join(transfer_list, '\n') },
+  };
+
+  TemporaryFile zip_file;
+  BuildUpdatePackage(entries, zip_file.release());
 
   MemMapping map;
   ASSERT_TRUE(map.MapFile(zip_file.path));
@@ -587,14 +642,6 @@
 }
 
 TEST_F(UpdaterTest, brotli_new_data) {
-  // Create a zip file with new_data.
-  TemporaryFile zip_file;
-  FILE* zip_file_ptr = fdopen(zip_file.release(), "wb");
-  ZipWriter zip_writer(zip_file_ptr);
-
-  // Add a brotli compressed new data entry.
-  ASSERT_EQ(0, zip_writer.StartEntry("new.dat.br", 0));
-
   auto generator = []() { return rand() % 128; };
   // Generate 100 blocks of random data.
   std::string brotli_new_data;
@@ -602,16 +649,12 @@
   generate_n(back_inserter(brotli_new_data), 4096 * 100, generator);
 
   size_t encoded_size = BrotliEncoderMaxCompressedSize(brotli_new_data.size());
-  std::vector<uint8_t> encoded_data(encoded_size);
+  std::string encoded_data(encoded_size, 0);
   ASSERT_TRUE(BrotliEncoderCompress(
       BROTLI_DEFAULT_QUALITY, BROTLI_DEFAULT_WINDOW, BROTLI_DEFAULT_MODE, brotli_new_data.size(),
-      reinterpret_cast<const uint8_t*>(brotli_new_data.data()), &encoded_size, encoded_data.data()));
-
-  ASSERT_EQ(0, zip_writer.WriteBytes(encoded_data.data(), encoded_size));
-  ASSERT_EQ(0, zip_writer.FinishEntry());
-  // Add a dummy patch data.
-  ASSERT_EQ(0, zip_writer.StartEntry("patch_data", 0));
-  ASSERT_EQ(0, zip_writer.FinishEntry());
+      reinterpret_cast<const uint8_t*>(brotli_new_data.data()), &encoded_size,
+      reinterpret_cast<uint8_t*>(const_cast<char*>(encoded_data.data()))));
+  encoded_data.resize(encoded_size);
 
   // Write a few small chunks of new data, then a large chunk, and finally a few small chunks.
   // This helps us to catch potential short writes.
@@ -627,12 +670,15 @@
     "new 2,98,99",
     "new 2,99,100",
   };
-  ASSERT_EQ(0, zip_writer.StartEntry("transfer_list", 0));
-  std::string commands = android::base::Join(transfer_list, '\n');
-  ASSERT_EQ(0, zip_writer.WriteBytes(commands.data(), commands.size()));
-  ASSERT_EQ(0, zip_writer.FinishEntry());
-  ASSERT_EQ(0, zip_writer.Finish());
-  ASSERT_EQ(0, fclose(zip_file_ptr));
+
+  std::unordered_map<std::string, std::string> entries = {
+    { "new.dat.br", std::move(encoded_data) },
+    { "patch_data", "" },
+    { "transfer_list", android::base::Join(transfer_list, '\n') },
+  };
+
+  TemporaryFile zip_file;
+  BuildUpdatePackage(entries, zip_file.release());
 
   MemMapping map;
   ASSERT_TRUE(map.MapFile(zip_file.path));
diff --git a/tests/testdata/deflate_src.zip b/tests/testdata/deflate_src.zip
new file mode 100644
index 0000000..bdb2b32
--- /dev/null
+++ b/tests/testdata/deflate_src.zip
Binary files differ
diff --git a/tests/testdata/deflate_tgt.zip b/tests/testdata/deflate_tgt.zip
new file mode 100644
index 0000000..2a21760
--- /dev/null
+++ b/tests/testdata/deflate_tgt.zip
Binary files differ
diff --git a/tools/recovery_l10n/res/values-hi/strings.xml b/tools/recovery_l10n/res/values-hi/strings.xml
index a8a876e..65d0033 100644
--- a/tools/recovery_l10n/res/values-hi/strings.xml
+++ b/tools/recovery_l10n/res/values-hi/strings.xml
@@ -3,7 +3,7 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="recovery_installing" msgid="2013591905463558223">"सिस्टम अपडेट इंस्टॉल किया जा रहा है"</string>
     <string name="recovery_erasing" msgid="7334826894904037088">"मिटाया जा रहा है"</string>
-    <string name="recovery_no_command" msgid="4465476568623024327">"कोई आदेश नहीं"</string>
+    <string name="recovery_no_command" msgid="4465476568623024327">"कोई निर्देश नहीं मिला"</string>
     <string name="recovery_error" msgid="5748178989622716736">"गड़बड़ी!"</string>
     <string name="recovery_installing_security" msgid="9184031299717114342">"सुरक्षा अपडेट इंस्टॉल किया जा रहा है"</string>
 </resources>
diff --git a/tools/recovery_l10n/res/values-mr/strings.xml b/tools/recovery_l10n/res/values-mr/strings.xml
index 8cf86f7..5f82033 100644
--- a/tools/recovery_l10n/res/values-mr/strings.xml
+++ b/tools/recovery_l10n/res/values-mr/strings.xml
@@ -1,9 +1,9 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="recovery_installing" msgid="2013591905463558223">"सिस्टम अद्यतन स्थापित करीत आहे"</string>
+    <string name="recovery_installing" msgid="2013591905463558223">"सिस्टम अपडेट इंस्टॉल करत आहे"</string>
     <string name="recovery_erasing" msgid="7334826894904037088">"मिटवत आहे"</string>
-    <string name="recovery_no_command" msgid="4465476568623024327">"कोणताही आदेश नाही"</string>
-    <string name="recovery_error" msgid="5748178989622716736">"त्रुटी!"</string>
-    <string name="recovery_installing_security" msgid="9184031299717114342">"सुरक्षा अद्यतन स्थापित करीत आहे"</string>
+    <string name="recovery_no_command" msgid="4465476568623024327">"कोणतीही कमांड नाही"</string>
+    <string name="recovery_error" msgid="5748178989622716736">"एरर!"</string>
+    <string name="recovery_installing_security" msgid="9184031299717114342">"सुरक्षा अपडेट इंस्टॉल करत आहे"</string>
 </resources>
diff --git a/tools/recovery_l10n/res/values-pa/strings.xml b/tools/recovery_l10n/res/values-pa/strings.xml
index 8564c9c..27972d1 100644
--- a/tools/recovery_l10n/res/values-pa/strings.xml
+++ b/tools/recovery_l10n/res/values-pa/strings.xml
@@ -1,9 +1,9 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="recovery_installing" msgid="2013591905463558223">"ਸਿਸਟਮ ਅੱਪਡੇਟ ਸਥਾਪਤ ਕੀਤੀ ਜਾ ਰਹੀ ਹੈ"</string>
+    <string name="recovery_installing" msgid="2013591905463558223">"ਸਿਸਟਮ ਅੱਪਡੇਟ ਸਥਾਪਤ ਕੀਤਾ ਜਾ ਰਿਹਾ ਹੈ"</string>
     <string name="recovery_erasing" msgid="7334826894904037088">"ਮਿਟਾਈ ਜਾ ਰਹੀ ਹੈ"</string>
-    <string name="recovery_no_command" msgid="4465476568623024327">"ਕੋਈ ਕਮਾਂਡ ਨਹੀਂ"</string>
+    <string name="recovery_no_command" msgid="4465476568623024327">"ਕੋਈ ਆਦੇਸ਼ ਨਹੀਂ"</string>
     <string name="recovery_error" msgid="5748178989622716736">"ਅਸ਼ੁੱਧੀ!"</string>
-    <string name="recovery_installing_security" msgid="9184031299717114342">"ਸੁਰੱਖਿਆ ਅੱਪਡੇਟ ਸਥਾਪਤ ਕੀਤੀ ਜਾ ਰਹੀ ਹੈ"</string>
+    <string name="recovery_installing_security" msgid="9184031299717114342">"ਸੁਰੱਖਿਆ ਅੱਪਡੇਟ ਸਥਾਪਤ ਕੀਤਾ ਜਾ ਰਿਹਾ ਹੈ"</string>
 </resources>
diff --git a/tools/recovery_l10n/res/values-te/strings.xml b/tools/recovery_l10n/res/values-te/strings.xml
index cfb02c9..e35c82b 100644
--- a/tools/recovery_l10n/res/values-te/strings.xml
+++ b/tools/recovery_l10n/res/values-te/strings.xml
@@ -4,6 +4,6 @@
     <string name="recovery_installing" msgid="2013591905463558223">"సిస్టమ్ నవీకరణను ఇన్‍స్టాల్ చేస్తోంది"</string>
     <string name="recovery_erasing" msgid="7334826894904037088">"డేటాను తొలగిస్తోంది"</string>
     <string name="recovery_no_command" msgid="4465476568623024327">"ఆదేశం లేదు"</string>
-    <string name="recovery_error" msgid="5748178989622716736">"లోపం సంభవించింది!"</string>
+    <string name="recovery_error" msgid="5748178989622716736">"ఎర్రర్ సంభవించింది!"</string>
     <string name="recovery_installing_security" msgid="9184031299717114342">"భద్రతా నవీకరణను ఇన్‌స్టాల్ చేస్తోంది"</string>
 </resources>
diff --git a/update_verifier/update_verifier.cpp b/update_verifier/update_verifier.cpp
index faebbed..ba7b7ae 100644
--- a/update_verifier/update_verifier.cpp
+++ b/update_verifier/update_verifier.cpp
@@ -137,11 +137,12 @@
     LOG(ERROR) << "Error in parsing range string.";
     return false;
   }
+  range_count /= 2;
 
   std::vector<std::future<bool>> threads;
   size_t thread_num = std::thread::hardware_concurrency() ?: 4;
-  thread_num = std::min(thread_num, range_count / 2);
-  size_t group_range_count = range_count / thread_num;
+  thread_num = std::min(thread_num, range_count);
+  size_t group_range_count = (range_count + thread_num - 1) / thread_num;
 
   for (size_t t = 0; t < thread_num; t++) {
     auto thread_func = [t, group_range_count, &dm_block_device, &ranges, &partition]() {
@@ -154,7 +155,8 @@
         return false;
       }
 
-      for (size_t i = 1 + group_range_count * t; i < group_range_count * (t + 1) + 1; i += 2) {
+      for (size_t i = group_range_count * 2 * t + 1;
+           i < std::min(group_range_count * 2 * (t + 1) + 1, ranges.size()); i += 2) {
         unsigned int range_start, range_end;
         bool parse_status = android::base::ParseUint(ranges[i], &range_start);
         parse_status = parse_status && android::base::ParseUint(ranges[i + 1], &range_end);
diff --git a/updater/blockimg.cpp b/updater/blockimg.cpp
index ce3cea4..6c7b3ef 100644
--- a/updater/blockimg.cpp
+++ b/updater/blockimg.cpp
@@ -281,6 +281,11 @@
     // Wait for nti->writer to be non-null, indicating some of this data is wanted.
     pthread_mutex_lock(&nti->mu);
     while (nti->writer == nullptr) {
+      // End the new data receiver if we encounter an error when performing block image update.
+      if (!nti->receiver_available) {
+        pthread_mutex_unlock(&nti->mu);
+        return false;
+      }
       pthread_cond_wait(&nti->cv, &nti->mu);
     }
     pthread_mutex_unlock(&nti->mu);
@@ -316,6 +321,11 @@
     // Wait for nti->writer to be non-null, indicating some of this data is wanted.
     pthread_mutex_lock(&nti->mu);
     while (nti->writer == nullptr) {
+      // End the receiver if we encounter an error when performing block image update.
+      if (!nti->receiver_available) {
+        pthread_mutex_unlock(&nti->mu);
+        return false;
+      }
       pthread_cond_wait(&nti->cv, &nti->mu);
     }
     pthread_mutex_unlock(&nti->mu);
@@ -1591,29 +1601,44 @@
     }
   }
 
-  if (params.canwrite) {
-    pthread_join(params.thread, nullptr);
-
-    LOG(INFO) << "wrote " << params.written << " blocks; expected " << total_blocks;
-    LOG(INFO) << "stashed " << params.stashed << " blocks";
-    LOG(INFO) << "max alloc needed was " << params.buffer.size();
-
-    const char* partition = strrchr(blockdev_filename->data.c_str(), '/');
-    if (partition != nullptr && *(partition + 1) != 0) {
-      fprintf(cmd_pipe, "log bytes_written_%s: %zu\n", partition + 1, params.written * BLOCKSIZE);
-      fprintf(cmd_pipe, "log bytes_stashed_%s: %zu\n", partition + 1, params.stashed * BLOCKSIZE);
-      fflush(cmd_pipe);
-    }
-    // Delete stash only after successfully completing the update, as it may contain blocks needed
-    // to complete the update later.
-    DeleteStash(params.stashbase);
-  } else {
-    LOG(INFO) << "verified partition contents; update may be resumed";
-  }
-
   rc = 0;
 
 pbiudone:
+  if (params.canwrite) {
+    pthread_mutex_lock(&params.nti.mu);
+    if (params.nti.receiver_available) {
+      LOG(WARNING) << "new data receiver is still available after executing all commands.";
+    }
+    params.nti.receiver_available = false;
+    pthread_cond_broadcast(&params.nti.cv);
+    pthread_mutex_unlock(&params.nti.mu);
+    int ret = pthread_join(params.thread, nullptr);
+    if (ret != 0) {
+      LOG(WARNING) << "pthread join returned with " << strerror(ret);
+    }
+
+    if (rc == 0) {
+      LOG(INFO) << "wrote " << params.written << " blocks; expected " << total_blocks;
+      LOG(INFO) << "stashed " << params.stashed << " blocks";
+      LOG(INFO) << "max alloc needed was " << params.buffer.size();
+
+      const char* partition = strrchr(blockdev_filename->data.c_str(), '/');
+      if (partition != nullptr && *(partition + 1) != 0) {
+        fprintf(cmd_pipe, "log bytes_written_%s: %zu\n", partition + 1, params.written * BLOCKSIZE);
+        fprintf(cmd_pipe, "log bytes_stashed_%s: %zu\n", partition + 1, params.stashed * BLOCKSIZE);
+        fflush(cmd_pipe);
+      }
+      // Delete stash only after successfully completing the update, as it may contain blocks needed
+      // to complete the update later.
+      DeleteStash(params.stashbase);
+    }
+
+    pthread_mutex_destroy(&params.nti.mu);
+    pthread_cond_destroy(&params.nti.cv);
+  } else if (rc == 0) {
+    LOG(INFO) << "verified partition contents; update may be resumed";
+  }
+
   if (ota_fsync(params.fd) == -1) {
     failure_type = kFsyncFailure;
     PLOG(ERROR) << "fsync failed";