update_engine: Generate valid delta files for squashfs.

This patch generates a valid (but inefficient) payload when a delta
payload is requested for a squashfs filesystem.

Since we are reusing the full payload generator for this purpose, this
patch relaxes the requirement of chunk_size to be positive in the
full_update_generator.cc code, replacing it by the default value of
1 MiB when nothing is passed.

BUG=chromium:430950
TEST=FEATURES=test emerge-link update_engine
TEST=cros_generate_update_payload for a delta payload works with
squashfs
TEST=`cros flash` a squashfs device with a delta payload works.

Change-Id: I88c7a571874c4c4697f528d44c52091aa1aed0c5
Reviewed-on: https://chromium-review.googlesource.com/260444
Reviewed-by: Alex Deymo <deymo@chromium.org>
Tested-by: Alex Deymo <deymo@chromium.org>
Commit-Queue: Alex Deymo <deymo@chromium.org>
diff --git a/delta_performer_unittest.cc b/delta_performer_unittest.cc
index b366c7e..5109fa0 100644
--- a/delta_performer_unittest.cc
+++ b/delta_performer_unittest.cc
@@ -107,9 +107,6 @@
   kValidOperationData,
 };
 
-// Chuck size used for full payloads during test.
-size_t kDefaultFullChunkSize = 1024 * 1024;
-
 }  // namespace
 
 static void CompareFilesByBlock(const string& a_file, const string& b_file) {
@@ -489,8 +486,6 @@
           DeltaPerformer::kSupportedMinorPayloadVersion;
     } else {
       payload_config.minor_version = DeltaPerformer::kFullPayloadMinorVersion;
-      if (payload_config.chunk_size == -1)
-        payload_config.chunk_size = kDefaultFullChunkSize;
     }
     payload_config.target.rootfs_part = state->b_img;
     payload_config.target.rootfs_mountpt = b_mnt;
diff --git a/payload_generator/delta_diff_generator.cc b/payload_generator/delta_diff_generator.cc
index 3fc298e..1813abe 100644
--- a/payload_generator/delta_diff_generator.cc
+++ b/payload_generator/delta_diff_generator.cc
@@ -1050,16 +1050,28 @@
   // Select payload generation strategy based on the config.
   OperationsGenerator* strategy = nullptr;
   if (config.is_delta) {
-    // Delta update (with possibly a full kernel update).
-    if (config.minor_version == kInPlaceMinorPayloadVersion) {
-      LOG(INFO) << "Using generator InplaceGenerator::GenerateInplaceDelta";
-      strategy = &InplaceGenerator::GenerateInplaceDelta;
-    } else if (config.minor_version == kSourceMinorPayloadVersion) {
-      LOG(INFO) << "Using generator DeltaDiffGenerator::GenerateSourceDelta";
-      strategy = &DeltaDiffGenerator::GenerateDeltaWithSourceOperations;
+    // We don't efficiently support deltas on squashfs. For now, we will
+    // produce full operations in that case.
+    if (utils::IsSquashfsFilesystem(config.target.rootfs_part)) {
+      LOG(INFO) << "Using generator FullUpdateGenerator::Run for squashfs "
+                   "deltas";
+      strategy = &FullUpdateGenerator::Run;
+    } else if (utils::IsExtFilesystem(config.target.rootfs_part)) {
+      // Delta update (with possibly a full kernel update).
+      if (config.minor_version == kInPlaceMinorPayloadVersion) {
+        LOG(INFO) << "Using generator InplaceGenerator::GenerateInplaceDelta";
+        strategy = &InplaceGenerator::GenerateInplaceDelta;
+      } else if (config.minor_version == kSourceMinorPayloadVersion) {
+        LOG(INFO) << "Using generator DeltaDiffGenerator::GenerateSourceDelta";
+        strategy = &DeltaDiffGenerator::GenerateDeltaWithSourceOperations;
+      } else {
+        LOG(ERROR) << "Unsupported minor version given for delta payload: "
+                   << config.minor_version;
+        return false;
+      }
     } else {
-      LOG(ERROR) << "Unsupported minor version given for delta payload: "
-                 << config.minor_version;
+      LOG(ERROR) << "Unsupported filesystem for delta payload in "
+                 << config.target.rootfs_part;
       return false;
     }
   } else {
diff --git a/payload_generator/full_update_generator.cc b/payload_generator/full_update_generator.cc
index fef6f78..0259b7d 100644
--- a/payload_generator/full_update_generator.cc
+++ b/payload_generator/full_update_generator.cc
@@ -11,6 +11,7 @@
 #include <deque>
 #include <memory>
 
+#include <base/format_macros.h>
 #include <base/strings/stringprintf.h>
 #include <base/strings/string_util.h>
 
@@ -26,6 +27,8 @@
 
 namespace {
 
+const size_t kDefaultFullChunkSize = 1024 * 1024;  // 1 MiB
+
 // This class encapsulates a full update chunk processing thread. The processor
 // reads a chunk of data from the input file descriptor and compresses it. The
 // processor needs to be started through Start() then waited on through Wait().
@@ -115,9 +118,18 @@
     vector<DeltaArchiveManifest_InstallOperation>* kernel_ops,
     vector<Vertex::Index>* final_order) {
   TEST_AND_RETURN_FALSE(config.Validate());
+
   // FullUpdateGenerator requires a positive chuck_size, otherwise there will
   // be only one operation with the whole partition which should not be allowed.
-  TEST_AND_RETURN_FALSE(config.chunk_size > 0);
+  size_t full_chunk_size = kDefaultFullChunkSize;
+  if (config.chunk_size >= 0) {
+    full_chunk_size = config.chunk_size;
+  } else {
+    LOG(INFO) << "No chunk_size provided, using the default chunk_size for the "
+              << "full operations: " << full_chunk_size << " bytes.";
+  }
+  TEST_AND_RETURN_FALSE(full_chunk_size > 0);
+  TEST_AND_RETURN_FALSE(full_chunk_size % config.block_size == 0);
 
   const ImageConfig& target = config.target;  // Shortcut.
   size_t max_threads = std::max(sysconf(_SC_NPROCESSORS_ONLN), 4L);
@@ -134,17 +146,17 @@
     ScopedFdCloser in_fd_closer(&in_fd);
     deque<shared_ptr<ChunkProcessor>> threads;
     int last_progress_update = INT_MIN;
-    off_t bytes_left = part_sizes[partition], counter = 0, offset = 0;
+    size_t bytes_left = part_sizes[partition], counter = 0, offset = 0;
     while (bytes_left > 0 || !threads.empty()) {
       // Check and start new chunk processors if possible.
       while (threads.size() < max_threads && bytes_left > 0) {
+        size_t this_chunk_bytes = std::min(bytes_left, full_chunk_size);
         shared_ptr<ChunkProcessor> processor(
-            new ChunkProcessor(in_fd, offset,
-                               std::min(bytes_left, config.chunk_size)));
+            new ChunkProcessor(in_fd, offset, this_chunk_bytes));
         threads.push_back(processor);
         TEST_AND_RETURN_FALSE(processor->Start());
-        bytes_left -= config.chunk_size;
-        offset += config.chunk_size;
+        bytes_left -= this_chunk_bytes;
+        offset += this_chunk_bytes;
       }
 
       // Need to wait for a chunk processor to complete and process its output
@@ -157,7 +169,7 @@
       if (partition == 0) {
         graph->emplace_back();
         graph->back().file_name =
-            base::StringPrintf("<rootfs-operation-%" PRIi64 ">", counter++);
+            base::StringPrintf("<rootfs-operation-%" PRIuS ">", counter++);
         op = &graph->back().op;
         final_order->push_back(graph->size() - 1);
       } else {
@@ -178,7 +190,7 @@
       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(config.chunk_size / config.block_size);
+      dst_extent->set_num_blocks(full_chunk_size / config.block_size);
 
       int progress = static_cast<int>(
           (processor->offset() + processor->buffer_in().size()) * 100.0 /
diff --git a/payload_generator/generate_delta_main.cc b/payload_generator/generate_delta_main.cc
index 2cb8de2..1b558be 100644
--- a/payload_generator/generate_delta_main.cc
+++ b/payload_generator/generate_delta_main.cc
@@ -400,11 +400,6 @@
     }
   }
 
-  // Full payloads use a hard-coded chunk_size of 1 MiB.
-  if (!payload_config.is_delta) {
-    payload_config.chunk_size = 1024 * 1024;
-  }
-
   // Load the rootfs size from verity's kernel command line if rootfs
   // verification is enabled.
   payload_config.source.LoadVerityRootfsSize();
diff --git a/utils.cc b/utils.cc
index 7b3c6d0..c076682 100644
--- a/utils.cc
+++ b/utils.cc
@@ -811,6 +811,24 @@
   return true;
 }
 
+bool IsExtFilesystem(const std::string& device) {
+  chromeos::Blob header;
+  // The first 2 KiB is enough to read the ext2 superblock (located at offset
+  // 1024).
+  if (!ReadFileChunk(device, 0, 2048, &header))
+    return false;
+  return GetExt3Size(header.data(), header.size(), nullptr, nullptr);
+}
+
+bool IsSquashfsFilesystem(const std::string& device) {
+  chromeos::Blob header;
+  // The first 96 is enough to read the squashfs superblock.
+  const ssize_t kSquashfsSuperBlockSize = 96;
+  if (!ReadFileChunk(device, 0, kSquashfsSuperBlockSize, &header))
+    return false;
+  return GetSquashfs4Size(header.data(), header.size(), nullptr, nullptr);
+}
+
 string GetPathOnBoard(const string& command) {
   int return_code = 0;
   string command_path;
diff --git a/utils.h b/utils.h
index c820ab2..2374a94 100644
--- a/utils.h
+++ b/utils.h
@@ -237,6 +237,16 @@
                       int* out_block_count,
                       int* out_block_size);
 
+// Returns whether the filesystem is an ext[234] filesystem. In case of failure,
+// such as if the file |device| doesn't exists or can't be read, it returns
+// false.
+bool IsExtFilesystem(const std::string& device);
+
+// Returns whether the filesystem is a squashfs filesystem. In case of failure,
+// such as if the file |device| doesn't exists or can't be read, it returns
+// false.
+bool IsSquashfsFilesystem(const std::string& device);
+
 // Returns the path of the passed |command| on the board. This uses the
 // environment variable SYSROOT to determine the path to the command on the
 // board instead of the path on the running environment.