AU: Split applied update verification into a separate step.

Use instances of FilesystemCopierAction to do applied update verification. This
speeds it up slightly because asynchronous reads happen in parallel with hash
calculation but, more importantly, makes update_engine be responsive to D-Bus
during that step.

BUG=9140
TEST=unit tests, tested on device

Change-Id: I3ec9445de5e8258a63433a61f1a476aef4434f6c

Review URL: http://codereview.chromium.org/5516009
diff --git a/action_processor.h b/action_processor.h
index 8bd890b..0cf6ff6 100644
--- a/action_processor.h
+++ b/action_processor.h
@@ -36,8 +36,10 @@
   kActionCodeDownloadHashMismatchError = 10,
   kActionCodeDownloadSizeMismatchError = 11,
   kActionCodeDownloadPayloadVerificationError = 12,
-  kActionCodeDownloadAppliedUpdateVerificationError = 13,
+  kActionCodeDownloadNewPartitionInfoError = 13,
   kActionCodeDownloadWriteError = 14,
+  kActionCodeNewRootfsVerificationError = 15,
+  kActionCodeNewKernelVerificationError = 16,
   kActionCodeOmahaRequestEmptyResponseError = 200,
   kActionCodeOmahaRequestXMLParseError = 201,
   kActionCodeOmahaRequestNoUpdateCheckNode = 202,
diff --git a/delta_performer.cc b/delta_performer.cc
index a5b7a72..6eb8aea 100644
--- a/delta_performer.cc
+++ b/delta_performer.cc
@@ -174,8 +174,8 @@
   if (OmahaHashCalculator::Base64Encode(info.hash().data(),
                                         info.hash().size(),
                                         &sha256)) {
-    LOG(INFO) << "PartitionInfo " << tag << " sha256:" << sha256
-              << " length:" << tag.size();
+    LOG(INFO) << "PartitionInfo " << tag << " sha256: " << sha256
+              << " size: " << info.size();
   } else {
     LOG(ERROR) << "Base64Encode failed for tag: " << tag;
   }
@@ -589,25 +589,21 @@
   return true;
 }
 
-bool DeltaPerformer::VerifyAppliedUpdate(const string& path,
-                                         const string& kernel_path) {
-  LOG(INFO) << "Verifying applied update.";
+bool DeltaPerformer::GetNewPartitionInfo(uint64_t* kernel_size,
+                                         vector<char>* kernel_hash,
+                                         uint64_t* rootfs_size,
+                                         vector<char>* rootfs_hash) {
   TEST_AND_RETURN_FALSE(manifest_valid_ &&
                         manifest_.has_new_kernel_info() &&
                         manifest_.has_new_rootfs_info());
-  const string* paths[] = { &kernel_path, &path };
-  const PartitionInfo* infos[] = {
-    &manifest_.new_kernel_info(), &manifest_.new_rootfs_info()
-  };
-  for (size_t i = 0; i < arraysize(paths); ++i) {
-    OmahaHashCalculator hasher;
-    TEST_AND_RETURN_FALSE(hasher.UpdateFile(*paths[i], infos[i]->size()));
-    TEST_AND_RETURN_FALSE(hasher.Finalize());
-    TEST_AND_RETURN_FALSE(hasher.raw_hash().size() == infos[i]->hash().size());
-    TEST_AND_RETURN_FALSE(memcmp(hasher.raw_hash().data(),
-                                 infos[i]->hash().data(),
-                                 hasher.raw_hash().size()) == 0);
-  }
+  *kernel_size = manifest_.new_kernel_info().size();
+  *rootfs_size = manifest_.new_rootfs_info().size();
+  vector<char> new_kernel_hash(manifest_.new_kernel_info().hash().begin(),
+                               manifest_.new_kernel_info().hash().end());
+  vector<char> new_rootfs_hash(manifest_.new_rootfs_info().hash().begin(),
+                               manifest_.new_rootfs_info().hash().end());
+  kernel_hash->swap(new_kernel_hash);
+  rootfs_hash->swap(new_rootfs_hash);
   return true;
 }
 
@@ -616,19 +612,19 @@
   CHECK(manifest_valid_);
   if (manifest_.has_old_kernel_info()) {
     const PartitionInfo& info = manifest_.old_kernel_info();
-    TEST_AND_RETURN_FALSE(current_kernel_hash_ != NULL &&
-                          current_kernel_hash_->size() == info.hash().size() &&
-                          memcmp(current_kernel_hash_->data(),
+    TEST_AND_RETURN_FALSE(!current_kernel_hash_.empty() &&
+                          current_kernel_hash_.size() == info.hash().size() &&
+                          memcmp(current_kernel_hash_.data(),
                                  info.hash().data(),
-                                 current_kernel_hash_->size()) == 0);
+                                 current_kernel_hash_.size()) == 0);
   }
   if (manifest_.has_old_rootfs_info()) {
     const PartitionInfo& info = manifest_.old_rootfs_info();
-    TEST_AND_RETURN_FALSE(current_rootfs_hash_ != NULL &&
-                          current_rootfs_hash_->size() == info.hash().size() &&
-                          memcmp(current_rootfs_hash_->data(),
+    TEST_AND_RETURN_FALSE(!current_rootfs_hash_.empty() &&
+                          current_rootfs_hash_.size() == info.hash().size() &&
+                          memcmp(current_rootfs_hash_.data(),
                                  info.hash().data(),
-                                 current_rootfs_hash_->size()) == 0);
+                                 current_rootfs_hash_.size()) == 0);
   }
   return true;
 }
diff --git a/delta_performer.h b/delta_performer.h
index 0e3cb93..8a9914d 100644
--- a/delta_performer.h
+++ b/delta_performer.h
@@ -34,9 +34,7 @@
         next_operation_num_(0),
         buffer_offset_(0),
         last_updated_buffer_offset_(kuint64max),
-        block_size_(0),
-        current_kernel_hash_(NULL),
-        current_rootfs_hash_(NULL) {}
+        block_size_(0) {}
 
   // Opens the kernel. Should be called before or after Open(), but before
   // Write(). The kernel file will be close()d when Close() is called.
@@ -65,10 +63,16 @@
                      const std::string& update_check_response_hash,
                      const uint64_t update_check_response_size);
 
-  // Verifies that the generated update is correct based on the hashes sent by
-  // the server. Returns true on success, false otherwise.
-  bool VerifyAppliedUpdate(const std::string& path,
-                           const std::string& kernel_path);
+  // Reads from the update manifest the expected sizes and hashes of the target
+  // kernel and rootfs partitions. These values can be used for applied update
+  // hash verification. This method must be called after the update manifest has
+  // been parsed (e.g., after closing the stream). Returns true on success, and
+  // false on failure (e.g., when the values are not present in the update
+  // manifest).
+  bool GetNewPartitionInfo(uint64_t* kernel_size,
+                           std::vector<char>* kernel_hash,
+                           uint64_t* rootfs_size,
+                           std::vector<char>* rootfs_hash);
 
   // Converts an ordered collection of Extent objects which contain data of
   // length full_length to a comma-separated string. For each Extent, the
@@ -96,11 +100,11 @@
   // success, false otherwise.
   static bool ResetUpdateProgress(PrefsInterface* prefs, bool quick);
 
-  void set_current_kernel_hash(const std::vector<char>* hash) {
+  void set_current_kernel_hash(const std::vector<char>& hash) {
     current_kernel_hash_ = hash;
   }
 
-  void set_current_rootfs_hash(const std::vector<char>* hash) {
+  void set_current_rootfs_hash(const std::vector<char>& hash) {
     current_rootfs_hash_ = hash;
   }
 
@@ -199,8 +203,8 @@
 
   // Hashes for the current partitions to be used for source partition
   // verification.
-  const std::vector<char>* current_kernel_hash_;
-  const std::vector<char>* current_rootfs_hash_;
+  std::vector<char> current_kernel_hash_;
+  std::vector<char> current_rootfs_hash_;
 
   DISALLOW_COPY_AND_ASSIGN(DeltaPerformer);
 };
diff --git a/delta_performer_unittest.cc b/delta_performer_unittest.cc
index 42f237b..238731a 100755
--- a/delta_performer_unittest.cc
+++ b/delta_performer_unittest.cc
@@ -315,11 +315,11 @@
             OmahaHashCalculator::RawHashOfFile(a_img,
                                                image_size,
                                                &rootfs_hash));
-  performer.set_current_rootfs_hash(&rootfs_hash);
+  performer.set_current_rootfs_hash(rootfs_hash);
   vector<char> kernel_hash;
   EXPECT_TRUE(OmahaHashCalculator::RawHashOfData(old_kernel_data,
                                                  &kernel_hash));
-  performer.set_current_kernel_hash(&kernel_hash);
+  performer.set_current_kernel_hash(kernel_hash);
 
   EXPECT_EQ(0, performer.Open(a_img.c_str(), 0, 0));
   EXPECT_TRUE(performer.OpenKernel(old_kernel.c_str()));
@@ -347,7 +347,27 @@
       kUnittestPublicKeyPath,
       OmahaHashCalculator::OmahaHashOfData(delta),
       delta.size()));
-  EXPECT_TRUE(performer.VerifyAppliedUpdate(a_img, old_kernel));
+
+  uint64_t new_kernel_size;
+  vector<char> new_kernel_hash;
+  uint64_t new_rootfs_size;
+  vector<char> new_rootfs_hash;
+  EXPECT_TRUE(performer.GetNewPartitionInfo(&new_kernel_size,
+                                            &new_kernel_hash,
+                                            &new_rootfs_size,
+                                            &new_rootfs_hash));
+  EXPECT_EQ(4096, new_kernel_size);
+  vector<char> expected_new_kernel_hash;
+  EXPECT_TRUE(OmahaHashCalculator::RawHashOfData(new_kernel_data,
+                                                 &expected_new_kernel_hash));
+  EXPECT_TRUE(expected_new_kernel_hash == new_kernel_hash);
+  EXPECT_EQ(image_size, new_rootfs_size);
+  vector<char> expected_new_rootfs_hash;
+  EXPECT_EQ(image_size,
+            OmahaHashCalculator::RawHashOfFile(b_img,
+                                               image_size,
+                                               &expected_new_rootfs_hash));
+  EXPECT_TRUE(expected_new_rootfs_hash == new_rootfs_hash);
 }
 }
 
diff --git a/download_action.cc b/download_action.cc
index d06fb79..d6ae97d 100644
--- a/download_action.cc
+++ b/download_action.cc
@@ -65,10 +65,8 @@
       writer_ = decompressing_file_writer_.get();
     } else {
       delta_performer_.reset(new DeltaPerformer(prefs_));
-      delta_performer_->set_current_kernel_hash(
-          &install_plan_.current_kernel_hash);
-      delta_performer_->set_current_rootfs_hash(
-          &install_plan_.current_rootfs_hash);
+      delta_performer_->set_current_kernel_hash(install_plan_.kernel_hash);
+      delta_performer_->set_current_rootfs_hash(install_plan_.rootfs_hash);
       writer_ = delta_performer_.get();
     }
   }
@@ -169,11 +167,13 @@
         LOG(ERROR) << "Download of " << install_plan_.download_url
                    << " failed due to payload verification error.";
         code = kActionCodeDownloadPayloadVerificationError;
-      } else if (!delta_performer_->VerifyAppliedUpdate(
-          install_plan_.install_path, install_plan_.kernel_install_path)) {
-        LOG(ERROR) << "Download of " << install_plan_.download_url
-                   << " failed due to applied update verification error.";
-        code = kActionCodeDownloadAppliedUpdateVerificationError;
+      } else if (!delta_performer_->GetNewPartitionInfo(
+          &install_plan_.kernel_size,
+          &install_plan_.kernel_hash,
+          &install_plan_.rootfs_size,
+          &install_plan_.rootfs_hash)) {
+        LOG(ERROR) << "Unable to get new partition hash info.";
+        code = kActionCodeDownloadNewPartitionInfoError;
       }
     } else {
       // Makes sure the hash and size are correct for an old-style full update.
@@ -196,7 +196,7 @@
 
   // Write the path to the output pipe if we're successful.
   if (code == kActionCodeSuccess && HasOutputPipe())
-    SetOutputObject(GetInputObject());
+    SetOutputObject(install_plan_);
   processor_->ActionComplete(this, code);
 }
 
diff --git a/filesystem_copier_action.cc b/filesystem_copier_action.cc
index f3f71bd..7da54a8 100755
--- a/filesystem_copier_action.cc
+++ b/filesystem_copier_action.cc
@@ -32,12 +32,14 @@
 namespace chromeos_update_engine {
 
 namespace {
-const off_t kCopyFileBufferSize = 512 * 1024;
+const off_t kCopyFileBufferSize = 128 * 1024;
 }  // namespace {}
 
 FilesystemCopierAction::FilesystemCopierAction(
-    bool copying_kernel_install_path)
+    bool copying_kernel_install_path,
+    bool verify_hash)
     : copying_kernel_install_path_(copying_kernel_install_path),
+      verify_hash_(verify_hash),
       src_stream_(NULL),
       dst_stream_(NULL),
       read_done_(false),
@@ -68,44 +70,49 @@
   }
   install_plan_ = GetInputObject();
 
-  if (install_plan_.is_full_update || install_plan_.is_resume) {
-    // No copy needed. Done!
+  // Note that we do need to run hash verification for new-style full updates
+  // but currently the |is_full_update| field is set to true only for old-style
+  // full updates and we don't have any expected partition info in that case.
+  if (install_plan_.is_full_update ||
+      (!verify_hash_ && install_plan_.is_resume)) {
+    // No copy or hash verification needed. Done!
     if (HasOutputPipe())
       SetOutputObject(install_plan_);
     abort_action_completer.set_code(kActionCodeSuccess);
     return;
   }
 
-  string source = copy_source_;
+  const string destination = copying_kernel_install_path_ ?
+      install_plan_.kernel_install_path :
+      install_plan_.install_path;
+  string source = verify_hash_ ? destination : copy_source_;
   if (source.empty()) {
     source = copying_kernel_install_path_ ?
         utils::BootKernelDevice(utils::BootDevice()) :
         utils::BootDevice();
   }
-
-  const string destination = copying_kernel_install_path_ ?
-      install_plan_.kernel_install_path :
-      install_plan_.install_path;
-
   int src_fd = open(source.c_str(), O_RDONLY);
   if (src_fd < 0) {
     PLOG(ERROR) << "Unable to open " << source << " for reading:";
     return;
   }
-  int dst_fd = open(destination.c_str(),
-                    O_WRONLY | O_TRUNC | O_CREAT,
+
+  if (!verify_hash_) {
+    int dst_fd = open(destination.c_str(),
+                      O_WRONLY | O_TRUNC | O_CREAT,
                     0644);
-  if (dst_fd < 0) {
-    close(src_fd);
-    PLOG(ERROR) << "Unable to open " << install_plan_.install_path
-                << " for writing:";
-    return;
+    if (dst_fd < 0) {
+      close(src_fd);
+      PLOG(ERROR) << "Unable to open " << install_plan_.install_path
+                  << " for writing:";
+      return;
+    }
+
+    dst_stream_ = g_unix_output_stream_new(dst_fd, TRUE);
   }
 
   DetermineFilesystemSize(src_fd);
-
   src_stream_ = g_unix_input_stream_new(src_fd, TRUE);
-  dst_stream_ = g_unix_output_stream_new(dst_fd, TRUE);
 
   for (int i = 0; i < 2; i++) {
     buffer_[i].resize(kCopyFileBufferSize);
@@ -126,22 +133,22 @@
   }
 }
 
-void FilesystemCopierAction::Cleanup(bool success) {
+void FilesystemCopierAction::Cleanup(ActionExitCode code) {
   for (int i = 0; i < 2; i++) {
     g_object_unref(canceller_[i]);
     canceller_[i] = NULL;
   }
   g_object_unref(src_stream_);
   src_stream_ = NULL;
-  g_object_unref(dst_stream_);
-  dst_stream_ = NULL;
+  if (dst_stream_) {
+    g_object_unref(dst_stream_);
+    dst_stream_ = NULL;
+  }
   if (cancelled_)
     return;
-  if (success && HasOutputPipe())
+  if (code == kActionCodeSuccess && HasOutputPipe())
     SetOutputObject(install_plan_);
-  processor_->ActionComplete(
-      this,
-      success ? kActionCodeSuccess : kActionCodeError);
+  processor_->ActionComplete(this, code);
 }
 
 void FilesystemCopierAction::AsyncReadReadyCallback(GObject *source_object,
@@ -164,19 +171,21 @@
   } else {
     buffer_valid_size_[index] = bytes_read;
     buffer_state_[index] = kBufferStateFull;
-  }
-
-  if (bytes_read > 0) {
     filesystem_size_ -= bytes_read;
   }
-
   SpawnAsyncActions();
 
   if (bytes_read > 0) {
+    // If read_done_ is set, SpawnAsyncActions may finalize the hash so the hash
+    // update below would happen too late.
+    CHECK(!read_done_);
     if (!hasher_.Update(buffer_[index].data(), bytes_read)) {
       LOG(ERROR) << "Unable to update the hash.";
       failed_ = true;
     }
+    if (verify_hash_) {
+      buffer_state_[index] = kBufferStateEmpty;
+    }
   }
 }
 
@@ -235,23 +244,26 @@
   }
   if (failed_ || cancelled_) {
     if (!reading && !writing) {
-      Cleanup(false);
+      Cleanup(kActionCodeError);
     }
     return;
   }
   for (int i = 0; i < 2; i++) {
     if (!reading && !read_done_ && buffer_state_[i] == kBufferStateEmpty) {
+      int64_t bytes_to_read = std::min(static_cast<int64_t>(buffer_[0].size()),
+                                       filesystem_size_);
       g_input_stream_read_async(
           src_stream_,
           buffer_[i].data(),
-          GetBytesToRead(),
+          bytes_to_read,
           G_PRIORITY_DEFAULT,
           canceller_[i],
           &FilesystemCopierAction::StaticAsyncReadReadyCallback,
           this);
       reading = true;
       buffer_state_[i] = kBufferStateReading;
-    } else if (!writing && buffer_state_[i] == kBufferStateFull) {
+    } else if (!writing && !verify_hash_ &&
+               buffer_state_[i] == kBufferStateFull) {
       g_output_stream_write_async(
           dst_stream_,
           buffer_[i].data(),
@@ -266,22 +278,43 @@
   }
   if (!reading && !writing) {
     // We're done!
+    ActionExitCode code = kActionCodeSuccess;
     if (hasher_.Finalize()) {
-      LOG(INFO) << "hash: " << hasher_.hash();
-      if (copying_kernel_install_path_) {
-        install_plan_.current_kernel_hash = hasher_.raw_hash();
+      LOG(INFO) << "Hash: " << hasher_.hash();
+      if (verify_hash_) {
+        if (copying_kernel_install_path_) {
+          if (install_plan_.kernel_hash != hasher_.raw_hash()) {
+            code = kActionCodeNewKernelVerificationError;
+            LOG(ERROR) << "New kernel verification failed.";
+          }
+        } else {
+          if (install_plan_.rootfs_hash != hasher_.raw_hash()) {
+            code = kActionCodeNewRootfsVerificationError;
+            LOG(ERROR) << "New rootfs verification failed.";
+          }
+        }
       } else {
-        install_plan_.current_rootfs_hash = hasher_.raw_hash();
+        if (copying_kernel_install_path_) {
+          install_plan_.kernel_hash = hasher_.raw_hash();
+        } else {
+          install_plan_.rootfs_hash = hasher_.raw_hash();
+        }
       }
-      Cleanup(true);
     } else {
       LOG(ERROR) << "Unable to finalize the hash.";
-      Cleanup(false);
+      code = kActionCodeError;
     }
+    Cleanup(code);
   }
 }
 
 void FilesystemCopierAction::DetermineFilesystemSize(int fd) {
+  if (verify_hash_) {
+    filesystem_size_ = copying_kernel_install_path_ ?
+        install_plan_.kernel_size : install_plan_.rootfs_size;
+    LOG(INFO) << "Filesystem size: " << filesystem_size_;
+    return;
+  }
   filesystem_size_ = kint64max;
   int block_count = 0, block_size = 0;
   if (!copying_kernel_install_path_ &&
@@ -292,8 +325,4 @@
   }
 }
 
-int64_t FilesystemCopierAction::GetBytesToRead() {
-  return std::min(static_cast<int64_t>(buffer_[0].size()), filesystem_size_);
-}
-
 }  // namespace chromeos_update_engine
diff --git a/filesystem_copier_action.h b/filesystem_copier_action.h
index 6dbde41..0658597 100644
--- a/filesystem_copier_action.h
+++ b/filesystem_copier_action.h
@@ -37,7 +37,8 @@
 
 class FilesystemCopierAction : public Action<FilesystemCopierAction> {
  public:
-  explicit FilesystemCopierAction(bool copying_kernel_install_path);
+  FilesystemCopierAction(bool copying_kernel_install_path,
+                         bool verify_hash);
 
   typedef ActionTraits<FilesystemCopierAction>::InputObjectType
   InputObjectType;
@@ -58,7 +59,8 @@
   FRIEND_TEST(FilesystemCopierActionTest, RunAsRootDetermineFilesystemSizeTest);
 
   // Ping-pong buffers generally cycle through the following states:
-  // Empty->Reading->Full->Writing->Empty.
+  // Empty->Reading->Full->Writing->Empty. In hash verification mode the state
+  // is never set to Writing.
   enum BufferState {
     kBufferStateEmpty,
     kBufferStateReading,
@@ -82,23 +84,24 @@
   void SpawnAsyncActions();
 
   // Cleans up all the variables we use for async operations and tells the
-  // ActionProcessor we're done w/ success as passed in. |cancelled_| should be
+  // ActionProcessor we're done w/ |code| as passed in. |cancelled_| should be
   // true if TerminateProcessing() was called.
-  void Cleanup(bool success);
+  void Cleanup(ActionExitCode code);
 
   // Determine, if possible, the source file system size to avoid copying the
   // whole partition. Currently this supports only the root file system assuming
   // it's ext3-compatible.
   void DetermineFilesystemSize(int fd);
 
-  // Returns the number of bytes to read based on the size of the buffer and the
-  // filesystem size.
-  int64_t GetBytesToRead();
-
   // If true, this action is copying to the kernel_install_path from
   // the install plan, otherwise it's copying just to the install_path.
   const bool copying_kernel_install_path_;
 
+  // If true, this action is running in applied update hash verification mode --
+  // it computes a hash for the target install path and compares it against the
+  // expected value.
+  const bool verify_hash_;
+
   // The path to copy from. If empty (the default), the source is from the
   // passed in InstallPlan.
   std::string copy_source_;
diff --git a/filesystem_copier_action_unittest.cc b/filesystem_copier_action_unittest.cc
index 032a24d..2ce3cc4 100644
--- a/filesystem_copier_action_unittest.cc
+++ b/filesystem_copier_action_unittest.cc
@@ -27,9 +27,12 @@
 
 class FilesystemCopierActionTest : public ::testing::Test {
  protected:
+  // |verify_hash|: 0 - no hash verification, 1 -- successful hash verification,
+  // 2 -- hash verification failure.
   void DoTest(bool run_out_of_space,
               bool terminate_early,
-              bool use_kernel_partition);
+              bool use_kernel_partition,
+              int verify_hash);
   void SetUp() {
   }
   void TearDown() {
@@ -90,13 +93,14 @@
 
 TEST_F(FilesystemCopierActionTest, RunAsRootSimpleTest) {
   ASSERT_EQ(0, getuid());
-  DoTest(false, false, false);
-
-  DoTest(false, false, true);
+  DoTest(false, false, false, 0);
+  DoTest(false, false, true, 0);
 }
+
 void FilesystemCopierActionTest::DoTest(bool run_out_of_space,
                                         bool terminate_early,
-                                        bool use_kernel_partition) {
+                                        bool use_kernel_partition,
+                                        int verify_hash) {
   GMainLoop *loop = g_main_loop_new(g_main_context_default(), FALSE);
 
   string a_loop_file;
@@ -142,10 +146,29 @@
   // Set up the action objects
   InstallPlan install_plan;
   install_plan.is_full_update = false;
-  if (use_kernel_partition)
-    install_plan.kernel_install_path = b_dev;
-  else
-    install_plan.install_path = b_dev;
+  if (verify_hash) {
+    if (use_kernel_partition) {
+      install_plan.kernel_install_path = a_dev;
+      install_plan.kernel_size =
+          kLoopFileSize - ((verify_hash == 2) ? 1 : 0);
+      EXPECT_TRUE(OmahaHashCalculator::RawHashOfData(
+          a_loop_data,
+          &install_plan.kernel_hash));
+    } else {
+      install_plan.install_path = a_dev;
+      install_plan.rootfs_size =
+          kLoopFileSize - ((verify_hash == 2) ? 1 : 0);
+      EXPECT_TRUE(OmahaHashCalculator::RawHashOfData(
+          a_loop_data,
+          &install_plan.rootfs_hash));
+    }
+  } else {
+    if (use_kernel_partition) {
+      install_plan.kernel_install_path = b_dev;
+    } else {
+      install_plan.install_path = b_dev;
+    }
+  }
 
   ActionProcessor processor;
   FilesystemCopierActionTestDelegate delegate;
@@ -153,7 +176,8 @@
   processor.set_delegate(&delegate);
 
   ObjectFeederAction<InstallPlan> feeder_action;
-  FilesystemCopierAction copier_action(use_kernel_partition);
+  FilesystemCopierAction copier_action(use_kernel_partition,
+                                       verify_hash != 0);
   ObjectCollectorAction<InstallPlan> collector_action;
 
   BondActions(&feeder_action, &copier_action);
@@ -163,7 +187,9 @@
   processor.EnqueueAction(&copier_action);
   processor.EnqueueAction(&collector_action);
 
-  copier_action.set_copy_source(a_dev);
+  if (!verify_hash) {
+    copier_action.set_copy_source(a_dev);
+  }
   feeder_action.set_obj(install_plan);
 
   StartProcessorCallbackArgs start_callback_args;
@@ -181,21 +207,26 @@
     EXPECT_EQ(kActionCodeError, delegate.code());
     return;
   }
+  if (verify_hash == 2) {
+    EXPECT_EQ(use_kernel_partition ?
+              kActionCodeNewKernelVerificationError :
+              kActionCodeNewRootfsVerificationError,
+              delegate.code());
+    return;
+  }
   EXPECT_EQ(kActionCodeSuccess, delegate.code());
 
   // Make sure everything in the out_image is there
   vector<char> a_out;
-  vector<char> b_out;
   EXPECT_TRUE(utils::ReadFile(a_dev, &a_out));
-  EXPECT_TRUE(utils::ReadFile(b_dev, &b_out));
-  EXPECT_TRUE(ExpectVectorsEq(a_out, b_out));
+  if (!verify_hash) {
+    vector<char> b_out;
+    EXPECT_TRUE(utils::ReadFile(b_dev, &b_out));
+    EXPECT_TRUE(ExpectVectorsEq(a_out, b_out));
+  }
   EXPECT_TRUE(ExpectVectorsEq(a_loop_data, a_out));
 
   EXPECT_TRUE(collector_action.object() == install_plan);
-  if (terminate_early) {
-    // sleep so OS can clean up
-    sleep(1);
-  }
 }
 
 class FilesystemCopierActionTest2Delegate : public ActionProcessorDelegate {
@@ -219,7 +250,7 @@
 
   processor.set_delegate(&delegate);
 
-  FilesystemCopierAction copier_action(false);
+  FilesystemCopierAction copier_action(false, false);
   ObjectCollectorAction<InstallPlan> collector_action;
 
   BondActions(&copier_action, &collector_action);
@@ -242,7 +273,7 @@
   const char* kUrl = "http://some/url";
   InstallPlan install_plan(true, false, kUrl, 0, "", "", "");
   feeder_action.set_obj(install_plan);
-  FilesystemCopierAction copier_action(false);
+  FilesystemCopierAction copier_action(false, false);
   ObjectCollectorAction<InstallPlan> collector_action;
 
   BondActions(&feeder_action, &copier_action);
@@ -268,7 +299,7 @@
   const char* kUrl = "http://some/url";
   InstallPlan install_plan(false, true, kUrl, 0, "", "", "");
   feeder_action.set_obj(install_plan);
-  FilesystemCopierAction copier_action(false);
+  FilesystemCopierAction copier_action(false, false);
   ObjectCollectorAction<InstallPlan> collector_action;
 
   BondActions(&feeder_action, &copier_action);
@@ -299,7 +330,7 @@
                            "/no/such/file",
                            "/no/such/file");
   feeder_action.set_obj(install_plan);
-  FilesystemCopierAction copier_action(false);
+  FilesystemCopierAction copier_action(false, false);
   ObjectCollectorAction<InstallPlan> collector_action;
 
   BondActions(&copier_action, &collector_action);
@@ -313,14 +344,26 @@
   EXPECT_EQ(kActionCodeError, delegate.code_);
 }
 
+TEST_F(FilesystemCopierActionTest, RunAsRootVerifyHashTest) {
+  ASSERT_EQ(0, getuid());
+  DoTest(false, false, false, 1);
+  DoTest(false, false, true, 1);
+}
+
+TEST_F(FilesystemCopierActionTest, RunAsRootVerifyHashFailTest) {
+  ASSERT_EQ(0, getuid());
+  DoTest(false, false, false, 2);
+  DoTest(false, false, true, 2);
+}
+
 TEST_F(FilesystemCopierActionTest, RunAsRootNoSpaceTest) {
   ASSERT_EQ(0, getuid());
-  DoTest(true, false, false);
+  DoTest(true, false, false, 0);
 }
 
 TEST_F(FilesystemCopierActionTest, RunAsRootTerminateEarlyTest) {
   ASSERT_EQ(0, getuid());
-  DoTest(false, true, false);
+  DoTest(false, true, false, 0);
 }
 
 TEST_F(FilesystemCopierActionTest, RunAsRootDetermineFilesystemSizeTest) {
@@ -336,7 +379,7 @@
 
   for (int i = 0; i < 2; ++i) {
     bool is_kernel = i == 1;
-    FilesystemCopierAction action(is_kernel);
+    FilesystemCopierAction action(is_kernel, false);
     EXPECT_EQ(kint64max, action.filesystem_size_);
     {
       int fd = HANDLE_EINTR(open(img.c_str(), O_RDONLY));
diff --git a/generate_delta_main.cc b/generate_delta_main.cc
index 39a0e08..6e9e553 100644
--- a/generate_delta_main.cc
+++ b/generate_delta_main.cc
@@ -90,8 +90,8 @@
     vector<char> root_hash(root_info.hash().begin(),
                            root_info.hash().end());
     DeltaPerformer performer(&prefs);
-    performer.set_current_kernel_hash(&kern_hash);
-    performer.set_current_rootfs_hash(&root_hash);
+    performer.set_current_kernel_hash(kern_hash);
+    performer.set_current_rootfs_hash(root_hash);
     CHECK_EQ(performer.Open(FLAGS_old_image.c_str(), 0, 0), 0);
     CHECK(performer.OpenKernel(FLAGS_old_kernel.c_str()));
     vector<char> buf(1024 * 1024);
diff --git a/install_plan.h b/install_plan.h
index 9911d32..08472c8 100644
--- a/install_plan.h
+++ b/install_plan.h
@@ -29,7 +29,9 @@
         size(size),
         download_hash(hash),
         install_path(install_path),
-        kernel_install_path(kernel_install_path) {}
+        kernel_install_path(kernel_install_path),
+        kernel_size(0),
+        rootfs_size(0) {}
   InstallPlan() : is_full_update(false), is_resume(false), size(0) {}
 
   bool is_full_update;
@@ -39,8 +41,22 @@
   std::string download_hash;  // hash of the data at the url
   std::string install_path;  // path to install device
   std::string kernel_install_path;  // path to kernel install device
-  std::vector<char> current_kernel_hash;  // computed by FileSystemCopierAction
-  std::vector<char> current_rootfs_hash;  // computed by FileSystemCopierAction
+
+  // The fields below are used for kernel and rootfs verification. The flow is:
+  //
+  // 1. FilesystemCopierAction(verify_hash=false) computes and fills in the
+  // source partition sizes and hashes.
+  //
+  // 2. DownloadAction verifies the source partition sizes and hashes against
+  // the expected values transmitted in the update manifest. It fills in the
+  // expected applied partition sizes and hashes based on the manifest.
+  //
+  // 4. FilesystemCopierAction(verify_hashes=true) computes and verifies the
+  // applied partition sizes and hashes against the expected values.
+  uint64_t kernel_size;
+  uint64_t rootfs_size;
+  std::vector<char> kernel_hash;
+  std::vector<char> rootfs_hash;
 
   bool operator==(const InstallPlan& that) const {
     return (is_full_update == that.is_full_update) &&
diff --git a/update_attempter.cc b/update_attempter.cc
index 529338a..85500e6 100644
--- a/update_attempter.cc
+++ b/update_attempter.cc
@@ -139,7 +139,7 @@
     LOG(ERROR) << "Unable to initialize Omaha request device params.";
     return;
   }
-  
+
   obeying_proxies_ = true;
   if (obey_proxies || proxy_manual_checks_ == 0) {
     LOG(INFO) << "forced to obey proxies";
@@ -175,9 +175,9 @@
   shared_ptr<OmahaResponseHandlerAction> response_handler_action(
       new OmahaResponseHandlerAction(prefs_));
   shared_ptr<FilesystemCopierAction> filesystem_copier_action(
-      new FilesystemCopierAction(false));
+      new FilesystemCopierAction(false, false));
   shared_ptr<FilesystemCopierAction> kernel_filesystem_copier_action(
-      new FilesystemCopierAction(true));
+      new FilesystemCopierAction(true, false));
   shared_ptr<OmahaRequestAction> download_started_action(
       new OmahaRequestAction(prefs_,
                              omaha_request_params_,
@@ -193,6 +193,10 @@
                              new OmahaEvent(
                                  OmahaEvent::kTypeUpdateDownloadFinished),
                              new LibcurlHttpFetcher(GetProxyResolver())));
+  shared_ptr<FilesystemCopierAction> filesystem_verifier_action(
+      new FilesystemCopierAction(false, true));
+  shared_ptr<FilesystemCopierAction> kernel_filesystem_verifier_action(
+      new FilesystemCopierAction(true, true));
   shared_ptr<PostinstallRunnerAction> postinstall_runner_action(
       new PostinstallRunnerAction);
   shared_ptr<OmahaRequestAction> update_complete_action(
@@ -213,6 +217,9 @@
   actions_.push_back(shared_ptr<AbstractAction>(download_started_action));
   actions_.push_back(shared_ptr<AbstractAction>(download_action));
   actions_.push_back(shared_ptr<AbstractAction>(download_finished_action));
+  actions_.push_back(shared_ptr<AbstractAction>(filesystem_verifier_action));
+  actions_.push_back(shared_ptr<AbstractAction>(
+      kernel_filesystem_verifier_action));
   actions_.push_back(shared_ptr<AbstractAction>(postinstall_runner_action));
   actions_.push_back(shared_ptr<AbstractAction>(update_complete_action));
 
@@ -233,6 +240,10 @@
   BondActions(kernel_filesystem_copier_action.get(),
               download_action.get());
   BondActions(download_action.get(),
+              filesystem_verifier_action.get());
+  BondActions(filesystem_verifier_action.get(),
+              kernel_filesystem_verifier_action.get());
+  BondActions(kernel_filesystem_verifier_action.get(),
               postinstall_runner_action.get());
 
   SetStatusAndNotify(UPDATE_STATUS_CHECKING_FOR_UPDATE);
diff --git a/update_attempter_unittest.cc b/update_attempter_unittest.cc
index 6d61c4e..2f60609 100644
--- a/update_attempter_unittest.cc
+++ b/update_attempter_unittest.cc
@@ -129,7 +129,7 @@
   EXPECT_EQ(kActionCodeOmahaResponseHandlerError,
             GetErrorCodeForAction(&omaha_response_handler_action,
                                   kActionCodeError));
-  FilesystemCopierAction filesystem_copier_action(false);
+  FilesystemCopierAction filesystem_copier_action(false, false);
   EXPECT_EQ(kActionCodeFilesystemCopierError,
             GetErrorCodeForAction(&filesystem_copier_action, kActionCodeError));
   PostinstallRunnerAction postinstall_runner_action;
@@ -236,6 +236,8 @@
     OmahaRequestAction::StaticType(),
     DownloadAction::StaticType(),
     OmahaRequestAction::StaticType(),
+    FilesystemCopierAction::StaticType(),
+    FilesystemCopierAction::StaticType(),
     PostinstallRunnerAction::StaticType(),
     OmahaRequestAction::StaticType()
   };