update_engine: Fix full payload generation when using chunk_size.

We already check that the partition size and the chunk_size are multiples
of the block_size, but we assume that the partition size is a multiple of the
chunk_size and that was mostly always the case. This patch fixes that by
setting the right operation size for the last rootfs and kernel operation.

BUG=chromium:485397
TEST=Added unit test to check this case.

Change-Id: Ia025d299c07bbe0732cf8554b75f4d40b6e063f9
Reviewed-on: https://chromium-review.googlesource.com/282758
Reviewed-by: Gabe Black <gabeblack@chromium.org>
Reviewed-by: Don Garrett <dgarrett@chromium.org>
Commit-Queue: Alex Deymo <deymo@chromium.org>
Trybot-Ready: Alex Deymo <deymo@chromium.org>
Tested-by: Alex Deymo <deymo@chromium.org>
diff --git a/payload_generator/full_update_generator.cc b/payload_generator/full_update_generator.cc
index b75e513..bc75393 100644
--- a/payload_generator/full_update_generator.cc
+++ b/payload_generator/full_update_generator.cc
@@ -190,7 +190,8 @@
       op->set_data_length(use_buf.size());
       Extent* dst_extent = op->add_dst_extents();
       dst_extent->set_start_block(processor->offset() / config.block_size);
-      dst_extent->set_num_blocks(full_chunk_size / config.block_size);
+      dst_extent->set_num_blocks(
+          processor->buffer_in().size() / config.block_size);
 
       int progress = static_cast<int>(
           (processor->offset() + processor->buffer_in().size()) * 100.0 /
diff --git a/payload_generator/full_update_generator_unittest.cc b/payload_generator/full_update_generator_unittest.cc
index cf8def1..0a04e4e 100644
--- a/payload_generator/full_update_generator_unittest.cc
+++ b/payload_generator/full_update_generator_unittest.cc
@@ -10,6 +10,7 @@
 #include <gtest/gtest.h>
 
 #include "update_engine/delta_performer.h"
+#include "update_engine/payload_generator/extent_utils.h"
 #include "update_engine/test_utils.h"
 
 using chromeos_update_engine::test_utils::FillWithData;
@@ -25,15 +26,42 @@
     config_.minor_version = DeltaPerformer::kFullPayloadMinorVersion;
     config_.chunk_size = 128 * 1024;
     config_.block_size = 4096;
+
+    EXPECT_TRUE(utils::MakeTempFile("FullUpdateTest_rootfs.XXXXXX",
+                                    &config_.target.rootfs.path,
+                                    nullptr));
+    EXPECT_TRUE(utils::MakeTempFile("FullUpdateTest_kernel.XXXXXX",
+                                    &config_.target.kernel.path,
+                                    nullptr));
+    EXPECT_TRUE(utils::MakeTempFile("FullUpdateTest_blobs.XXXXXX",
+                                    &out_blobs_path_,
+                                    &out_blobs_fd_));
+
+    rootfs_part_unlinker_.reset(
+        new ScopedPathUnlinker(config_.target.rootfs.path));
+    kernel_part_unlinker_.reset(
+        new ScopedPathUnlinker(config_.target.kernel.path));
+    out_blobs_unlinker_.reset(new ScopedPathUnlinker(out_blobs_path_));
   }
 
   PayloadGenerationConfig config_;
+
+  // Output file holding the payload blobs.
+  string out_blobs_path_;
+  int out_blobs_fd_{-1};
+  ScopedFdCloser out_blobs_fd_closer_{&out_blobs_fd_};
+
+  std::unique_ptr<ScopedPathUnlinker> rootfs_part_unlinker_;
+  std::unique_ptr<ScopedPathUnlinker> kernel_part_unlinker_;
+  std::unique_ptr<ScopedPathUnlinker> out_blobs_unlinker_;
+
+  // FullUpdateGenerator under test.
+  FullUpdateGenerator generator_;
 };
 
-
 TEST_F(FullUpdateGeneratorTest, RunTest) {
-  chromeos::Blob new_root(20 * 1024 * 1024);
-  chromeos::Blob new_kern(16 * 1024 * 1024);
+  chromeos::Blob new_root(9 * 1024 * 1024);
+  chromeos::Blob new_kern(3 * 1024 * 1024);
   FillWithData(&new_root);
   FillWithData(&new_kern);
 
@@ -42,39 +70,20 @@
   config_.target.rootfs.size = new_root.size() - 2 * 1024 * 1024;
   config_.target.kernel.size = new_kern.size();
 
-  EXPECT_TRUE(utils::MakeTempFile("NewFullUpdateTest_R.XXXXXX",
-                                  &config_.target.rootfs.path,
-                                  nullptr));
-  ScopedPathUnlinker rootfs_part_unlinker(config_.target.rootfs.path);
   EXPECT_TRUE(test_utils::WriteFileVector(config_.target.rootfs.path,
                                           new_root));
-
-  EXPECT_TRUE(utils::MakeTempFile("NewFullUpdateTest_K.XXXXXX",
-                                  &config_.target.kernel.path,
-                                  nullptr));
-  ScopedPathUnlinker kernel_path_unlinker(config_.target.kernel.path);
   EXPECT_TRUE(test_utils::WriteFileVector(config_.target.kernel.path,
                                           new_kern));
 
-  string out_blobs_path;
-  int out_blobs_fd;
-  EXPECT_TRUE(utils::MakeTempFile("NewFullUpdateTest_D.XXXXXX",
-                                  &out_blobs_path,
-                                  &out_blobs_fd));
-  ScopedPathUnlinker out_blobs_path_unlinker(out_blobs_path);
-  ScopedFdCloser out_blobs_fd_closer(&out_blobs_fd);
-
   off_t out_blobs_length = 0;
   vector<AnnotatedOperation> rootfs_ops;
   vector<AnnotatedOperation> kernel_ops;
 
-  FullUpdateGenerator generator;
-
-  EXPECT_TRUE(generator.GenerateOperations(config_,
-                                           out_blobs_fd,
-                                           &out_blobs_length,
-                                           &rootfs_ops,
-                                           &kernel_ops));
+  EXPECT_TRUE(generator_.GenerateOperations(config_,
+                                            out_blobs_fd_,
+                                            &out_blobs_length,
+                                            &rootfs_ops,
+                                            &kernel_ops));
   int64_t target_rootfs_chunks =
       config_.target.rootfs.size / config_.chunk_size;
   EXPECT_EQ(target_rootfs_chunks, rootfs_ops.size());
@@ -93,4 +102,41 @@
   }
 }
 
+// Test that if the chunk size is not a divisor of the image size, it handles
+// correctly the last chunk of each partition.
+TEST_F(FullUpdateGeneratorTest, ChunkSizeTooBig) {
+  config_.chunk_size = 1024 * 1024;
+  chromeos::Blob new_root(1536 * 1024);  // 1.5 MiB
+  chromeos::Blob new_kern(128 * 1024);
+  config_.rootfs_partition_size = new_root.size();
+  config_.target.rootfs.size = new_root.size();
+  config_.target.kernel.size = new_kern.size();
+
+  EXPECT_TRUE(test_utils::WriteFileVector(config_.target.rootfs.path,
+                                          new_root));
+  EXPECT_TRUE(test_utils::WriteFileVector(config_.target.kernel.path,
+                                          new_kern));
+
+  off_t out_blobs_length = 0;
+  vector<AnnotatedOperation> rootfs_ops;
+  vector<AnnotatedOperation> kernel_ops;
+
+  EXPECT_TRUE(generator_.GenerateOperations(config_,
+                                            out_blobs_fd_,
+                                            &out_blobs_length,
+                                            &rootfs_ops,
+                                            &kernel_ops));
+  // rootfs has one chunk and a half.
+  EXPECT_EQ(2, rootfs_ops.size());
+  EXPECT_EQ(config_.chunk_size / config_.block_size,
+            BlocksInExtents(rootfs_ops[0].op.dst_extents()));
+  EXPECT_EQ((new_root.size() - config_.chunk_size) / config_.block_size,
+            BlocksInExtents(rootfs_ops[1].op.dst_extents()));
+
+  // kernel has less than one chunk.
+  EXPECT_EQ(1, kernel_ops.size());
+  EXPECT_EQ(new_kern.size() / config_.block_size,
+            BlocksInExtents(kernel_ops[0].op.dst_extents()));
+}
+
 }  // namespace chromeos_update_engine