AU: support for generating new style full updates.

New style full updates are full updates that use the delta upload file
format. A full update consists of sequential operations to write the
full kernel/rootfs in chunks (currently 128KiB).

Because these full updates use the new file format, they get benefits
over the old type of full update:
- support for embedded signatures
- (eventual) support for resuming installation across reboot
- help reduce code needed in the updater client software

BUG=7248
TEST=generated/applied full update on host; unittests

Review URL: http://codereview.chromium.org/3550020
diff --git a/delta_diff_generator.cc b/delta_diff_generator.cc
index 676629a..9a5219d 100644
--- a/delta_diff_generator.cc
+++ b/delta_diff_generator.cc
@@ -6,6 +6,7 @@
 
 #include <errno.h>
 #include <fcntl.h>
+#include <inttypes.h>
 #include <sys/stat.h>
 #include <sys/types.h>
 
@@ -47,9 +48,10 @@
 typedef DeltaDiffGenerator::Block Block;
 
 namespace {
-const size_t kBlockSize = 4096;
+const size_t kBlockSize = 4096;  // bytes
 const size_t kRootFSPartitionSize = 1 * 1024 * 1024 * 1024;  // 1 GiB
 const uint64_t kVersionNumber = 1;
+const uint64_t kFullUpdateChunkSize = 128 * 1024;  // bytes
 
 // Stores all Extents for a file into 'out'. Returns true on success.
 bool GatherExtents(const string& path,
@@ -1119,6 +1121,82 @@
   return true;
 }
 
+bool DeltaDiffGenerator::ReadFullUpdateFromDisk(
+    Graph* graph,
+    const std::string& new_kernel_part,
+    const std::string& new_image,
+    int fd,
+    off_t* data_file_size,
+    off_t chunk_size,
+    vector<DeltaArchiveManifest_InstallOperation>* kernel_ops,
+    std::vector<Vertex::Index>* final_order) {
+  TEST_AND_RETURN_FALSE(chunk_size > 0);
+  TEST_AND_RETURN_FALSE((chunk_size % kBlockSize) == 0);
+  
+  // Get the sizes early in the function, so we can fail fast if the user
+  // passed us bad paths.
+  const off_t image_size = utils::FileSize(new_image);
+  TEST_AND_RETURN_FALSE(image_size >= 0);
+  const off_t kernel_size = utils::FileSize(new_kernel_part);
+  TEST_AND_RETURN_FALSE(kernel_size >= 0);
+
+  off_t part_sizes[] = { image_size, kernel_size };
+  string paths[] = { new_image, new_kernel_part };
+
+  for (int partition = 0; partition < 2; ++partition) {
+    const string& path = paths[partition];
+    LOG(INFO) << "compressing " << path;
+
+    int in_fd = open(path.c_str(), O_RDONLY, 0);
+    TEST_AND_RETURN_FALSE(in_fd >= 0);
+    ScopedFdCloser in_fd_closer(&in_fd);
+
+    for (off_t bytes_left = part_sizes[partition], counter = 0, offset = 0;
+         bytes_left > 0;
+         bytes_left -= chunk_size, ++counter, offset += chunk_size) {
+      LOG(INFO) << "offset = " << offset;
+      DeltaArchiveManifest_InstallOperation* op = NULL;
+      if (partition == 0) {
+        graph->resize(graph->size() + 1);
+        graph->back().file_name = path + StringPrintf("-%" PRIi64, counter);
+        op = &graph->back().op;
+        final_order->push_back(graph->size() - 1);
+      } else {
+        kernel_ops->resize(kernel_ops->size() + 1);
+        op = &kernel_ops->back();
+      }
+      LOG(INFO) << "have an op";
+      
+      vector<char> buf(min(bytes_left, chunk_size));
+      LOG(INFO) << "buf size: " << buf.size();
+      ssize_t bytes_read = -1;
+      
+      TEST_AND_RETURN_FALSE(utils::PReadAll(
+          in_fd, &buf[0], buf.size(), offset, &bytes_read));
+      TEST_AND_RETURN_FALSE(bytes_read == static_cast<ssize_t>(buf.size()));
+      
+      vector<char> buf_compressed;
+      
+      TEST_AND_RETURN_FALSE(BzipCompress(buf, &buf_compressed));
+      const bool compress = buf_compressed.size() < buf.size();
+      const vector<char>& use_buf = compress ? buf_compressed : buf;
+      if (compress) {
+        op->set_type(DeltaArchiveManifest_InstallOperation_Type_REPLACE_BZ);
+      } else {
+        op->set_type(DeltaArchiveManifest_InstallOperation_Type_REPLACE);
+      }
+      op->set_data_offset(*data_file_size);
+      *data_file_size += use_buf.size();
+      op->set_data_length(use_buf.size());
+      Extent* dst_extent = op->add_dst_extents();
+      dst_extent->set_start_block(offset / kBlockSize);
+      dst_extent->set_num_blocks(chunk_size / kBlockSize);
+    }
+  }
+
+  return true;
+}
+
 bool DeltaDiffGenerator::GenerateDeltaUpdateFile(
     const string& old_root,
     const string& old_image,
@@ -1129,18 +1207,23 @@
     const string& output_path,
     const string& private_key_path) {
   struct stat old_image_stbuf;
-  TEST_AND_RETURN_FALSE_ERRNO(stat(old_image.c_str(), &old_image_stbuf) == 0);
   struct stat new_image_stbuf;
   TEST_AND_RETURN_FALSE_ERRNO(stat(new_image.c_str(), &new_image_stbuf) == 0);
-  LOG_IF(WARNING, new_image_stbuf.st_size != old_image_stbuf.st_size)
-      << "Old and new images are different sizes.";
+  if (!old_image.empty()) {
+    TEST_AND_RETURN_FALSE_ERRNO(stat(old_image.c_str(), &old_image_stbuf) == 0);
+    LOG_IF(WARNING, new_image_stbuf.st_size != old_image_stbuf.st_size)
+        << "Old and new images are different sizes.";
+    LOG_IF(FATAL, old_image_stbuf.st_size % kBlockSize)
+        << "Old image not a multiple of block size " << kBlockSize;
+    // Sanity check kernel partition arg
+    TEST_AND_RETURN_FALSE(utils::FileSize(old_kernel_part) >= 0);
+  } else {
+    old_image_stbuf.st_size = 0;
+  }
   LOG_IF(FATAL, new_image_stbuf.st_size % kBlockSize)
       << "New image not a multiple of block size " << kBlockSize;
-  LOG_IF(FATAL, old_image_stbuf.st_size % kBlockSize)
-      << "Old image not a multiple of block size " << kBlockSize;
 
-  // Sanity check kernel partition args
-  TEST_AND_RETURN_FALSE(utils::FileSize(old_kernel_part) >= 0);
+  // Sanity check kernel partition arg
   TEST_AND_RETURN_FALSE(utils::FileSize(new_kernel_part) >= 0);
 
   vector<Block> blocks(max(old_image_stbuf.st_size / kBlockSize,
@@ -1163,7 +1246,8 @@
   vector<DeltaArchiveManifest_InstallOperation> kernel_ops;
 
   vector<Vertex::Index> final_order;
-  {
+  if (!old_image.empty()) {
+    // Delta update
     int fd;
     TEST_AND_RETURN_FALSE(
         utils::MakeTempFile(kTempFileTemplate, &temp_file_path, &fd));
@@ -1206,6 +1290,17 @@
                                             fd,
                                             &data_file_size,
                                             &final_order));
+  } else {
+    // Full update
+    int fd = 0;
+    TEST_AND_RETURN_FALSE(ReadFullUpdateFromDisk(&graph,
+                                                 new_kernel_part,
+                                                 new_image,
+                                                 fd,
+                                                 &data_file_size,
+                                                 kFullUpdateChunkSize,
+                                                 &kernel_ops,
+                                                 &final_order));
   }
 
   // Convert to protobuf Manifest object