update_engine: Remove copy from source to destination partition.

Removes the copy of the old rootfs and kernel to the new rootfs and
kernel. This was initially done in FilesystemCopierAction, which is now
called FilesystemVerifierAction.

When FilesystemVerifierAction is given a source rootfs or kernel, it
calculates the hashes. When asked to verify the new rootfs or kernel, it
computes the hashes and compares them against the values in the manifest.

BUG=chromium:463562
TEST=Updates using `cros flash --src-image-to-delta` and unit tests.
CQ-DEPEND=CL:267360

Change-Id: I3c9ba60fa4af0c5381ba8a10883bd47dc40adb8c
Reviewed-on: https://chromium-review.googlesource.com/267554
Reviewed-by: Allie Wood <alliewood@chromium.org>
Commit-Queue: Allie Wood <alliewood@chromium.org>
Trybot-Ready: Allie Wood <alliewood@chromium.org>
Tested-by: Allie Wood <alliewood@chromium.org>
diff --git a/delta_performer.cc b/delta_performer.cc
index cf37b2b..5f94e44 100644
--- a/delta_performer.cc
+++ b/delta_performer.cc
@@ -1386,15 +1386,16 @@
   if (manifest_.has_old_kernel_info()) {
     const PartitionInfo& info = manifest_.old_kernel_info();
     bool valid =
-        !install_plan_->kernel_hash.empty() &&
-        install_plan_->kernel_hash.size() == info.hash().size() &&
-        memcmp(install_plan_->kernel_hash.data(),
+        !install_plan_->source_kernel_hash.empty() &&
+        install_plan_->source_kernel_hash.size() == info.hash().size() &&
+        memcmp(install_plan_->source_kernel_hash.data(),
                info.hash().data(),
-               install_plan_->kernel_hash.size()) == 0;
+               install_plan_->source_kernel_hash.size()) == 0;
     if (!valid) {
       LogVerifyError(true,
-                     StringForHashBytes(install_plan_->kernel_hash.data(),
-                                        install_plan_->kernel_hash.size()),
+                     StringForHashBytes(
+                         install_plan_->source_kernel_hash.data(),
+                         install_plan_->source_kernel_hash.size()),
                      StringForHashBytes(info.hash().data(),
                                         info.hash().size()));
     }
@@ -1403,15 +1404,16 @@
   if (manifest_.has_old_rootfs_info()) {
     const PartitionInfo& info = manifest_.old_rootfs_info();
     bool valid =
-        !install_plan_->rootfs_hash.empty() &&
-        install_plan_->rootfs_hash.size() == info.hash().size() &&
-        memcmp(install_plan_->rootfs_hash.data(),
+        !install_plan_->source_rootfs_hash.empty() &&
+        install_plan_->source_rootfs_hash.size() == info.hash().size() &&
+        memcmp(install_plan_->source_rootfs_hash.data(),
                info.hash().data(),
-               install_plan_->rootfs_hash.size()) == 0;
+               install_plan_->source_rootfs_hash.size()) == 0;
     if (!valid) {
       LogVerifyError(false,
-                     StringForHashBytes(install_plan_->rootfs_hash.data(),
-                                        install_plan_->rootfs_hash.size()),
+                     StringForHashBytes(
+                         install_plan_->source_rootfs_hash.data(),
+                         install_plan_->source_rootfs_hash.size()),
                      StringForHashBytes(info.hash().data(),
                                         info.hash().size()));
     }
diff --git a/delta_performer_unittest.cc b/delta_performer_unittest.cc
index e969c57..87d98de 100644
--- a/delta_performer_unittest.cc
+++ b/delta_performer_unittest.cc
@@ -747,11 +747,13 @@
   DeltaPerformerTest::SetSupportedVersion(*performer, minor_version);
 
   EXPECT_EQ(state->image_size,
-            OmahaHashCalculator::RawHashOfFile(state->a_img,
-                                               state->image_size,
-                                               &install_plan.rootfs_hash));
-  EXPECT_TRUE(OmahaHashCalculator::RawHashOfData(state->old_kernel_data,
-                                                 &install_plan.kernel_hash));
+            OmahaHashCalculator::RawHashOfFile(
+                state->a_img,
+                state->image_size,
+                &install_plan.source_rootfs_hash));
+  EXPECT_TRUE(OmahaHashCalculator::RawHashOfData(
+                  state->old_kernel_data,
+                  &install_plan.source_kernel_hash));
 
   // With minor version 2, we want the target to be the new image, result_img,
   // but with version 1, we want to update A in place.
diff --git a/error_code.h b/error_code.h
index 6a47784..fde1833 100644
--- a/error_code.h
+++ b/error_code.h
@@ -58,6 +58,7 @@
   kUnsupportedMajorPayloadVersion = 44,
   kUnsupportedMinorPayloadVersion = 45,
   kOmahaRequestXMLHasEntityDecl = 46,
+  kFilesystemVerifierError = 47,
 
   // VERY IMPORTANT! When adding new error codes:
   //
diff --git a/filesystem_copier_action.cc b/filesystem_copier_action.cc
deleted file mode 100644
index 7346a99..0000000
--- a/filesystem_copier_action.cc
+++ /dev/null
@@ -1,352 +0,0 @@
-// Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "update_engine/filesystem_copier_action.h"
-
-#include <errno.h>
-#include <fcntl.h>
-#include <sys/stat.h>
-#include <sys/types.h>
-
-#include <algorithm>
-#include <cstdlib>
-#include <map>
-#include <string>
-#include <vector>
-
-#include <gio/gio.h>
-#include <gio/gunixinputstream.h>
-#include <gio/gunixoutputstream.h>
-#include <glib.h>
-
-#include "update_engine/glib_utils.h"
-#include "update_engine/hardware_interface.h"
-#include "update_engine/subprocess.h"
-#include "update_engine/system_state.h"
-#include "update_engine/utils.h"
-
-using std::map;
-using std::string;
-using std::vector;
-
-namespace chromeos_update_engine {
-
-namespace {
-const off_t kCopyFileBufferSize = 128 * 1024;
-}  // namespace
-
-FilesystemCopierAction::FilesystemCopierAction(
-    SystemState* system_state,
-    bool copying_kernel_install_path,
-    bool verify_hash)
-    : copying_kernel_install_path_(copying_kernel_install_path),
-      verify_hash_(verify_hash),
-      src_stream_(nullptr),
-      dst_stream_(nullptr),
-      read_done_(false),
-      failed_(false),
-      cancelled_(false),
-      filesystem_size_(kint64max),
-      system_state_(system_state) {
-  // A lot of code works on the implicit assumption that processing is done on
-  // exactly 2 ping-pong buffers.
-  COMPILE_ASSERT(arraysize(buffer_) == 2 &&
-                 arraysize(buffer_state_) == 2 &&
-                 arraysize(buffer_valid_size_) == 2 &&
-                 arraysize(canceller_) == 2,
-                 ping_pong_buffers_not_two);
-  for (int i = 0; i < 2; ++i) {
-    buffer_state_[i] = kBufferStateEmpty;
-    buffer_valid_size_[i] = 0;
-    canceller_[i] = nullptr;
-  }
-}
-
-void FilesystemCopierAction::PerformAction() {
-  // Will tell the ActionProcessor we've failed if we return.
-  ScopedActionCompleter abort_action_completer(processor_, this);
-
-  if (!HasInputObject()) {
-    LOG(ERROR) << "FilesystemCopierAction missing input object.";
-    return;
-  }
-  install_plan_ = GetInputObject();
-
-  // No need to copy on a resume.
-  if (!verify_hash_ && install_plan_.is_resume) {
-    // No copy or hash verification needed. Done!
-    LOG(INFO) << "filesystem copying skipped on resumed update.";
-    if (HasOutputPipe())
-      SetOutputObject(install_plan_);
-    abort_action_completer.set_code(ErrorCode::kSuccess);
-    return;
-  }
-
-  if (copying_kernel_install_path_) {
-    if (!system_state_->hardware()->MarkKernelUnbootable(
-        install_plan_.kernel_install_path)) {
-      PLOG(ERROR) << "Unable to clear kernel GPT boot flags: " <<
-          install_plan_.kernel_install_path;
-    }
-  }
-
-  if (!verify_hash_ && install_plan_.is_full_update) {
-    // No copy or hash verification needed. Done!
-    LOG(INFO) << "filesystem copying skipped on full update.";
-    if (HasOutputPipe())
-      SetOutputObject(install_plan_);
-    abort_action_completer.set_code(ErrorCode::kSuccess);
-    return;
-  }
-
-  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::KernelDeviceOfBootDevice(
-            system_state_->hardware()->BootDevice()) :
-        system_state_->hardware()->BootDevice();
-  }
-  int src_fd = open(source.c_str(), O_RDONLY);
-  if (src_fd < 0) {
-    PLOG(ERROR) << "Unable to open " << source << " for reading:";
-    return;
-  }
-
-  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;
-    }
-
-    dst_stream_ = g_unix_output_stream_new(dst_fd, TRUE);
-  }
-
-  DetermineFilesystemSize(src_fd);
-  src_stream_ = g_unix_input_stream_new(src_fd, TRUE);
-
-  for (int i = 0; i < 2; i++) {
-    buffer_[i].resize(kCopyFileBufferSize);
-    canceller_[i] = g_cancellable_new();
-  }
-
-  // Start the first read.
-  SpawnAsyncActions();
-
-  abort_action_completer.set_should_complete(false);
-}
-
-void FilesystemCopierAction::TerminateProcessing() {
-  for (int i = 0; i < 2; i++) {
-    if (canceller_[i]) {
-      g_cancellable_cancel(canceller_[i]);
-    }
-  }
-}
-
-bool FilesystemCopierAction::IsCleanupPending() const {
-  return (src_stream_ != nullptr);
-}
-
-void FilesystemCopierAction::Cleanup(ErrorCode code) {
-  for (int i = 0; i < 2; i++) {
-    g_object_unref(canceller_[i]);
-    canceller_[i] = nullptr;
-  }
-  g_object_unref(src_stream_);
-  src_stream_ = nullptr;
-  if (dst_stream_) {
-    g_object_unref(dst_stream_);
-    dst_stream_ = nullptr;
-  }
-  if (cancelled_)
-    return;
-  if (code == ErrorCode::kSuccess && HasOutputPipe())
-    SetOutputObject(install_plan_);
-  processor_->ActionComplete(this, code);
-}
-
-void FilesystemCopierAction::AsyncReadReadyCallback(GObject *source_object,
-                                                    GAsyncResult *res) {
-  int index = buffer_state_[0] == kBufferStateReading ? 0 : 1;
-  CHECK(buffer_state_[index] == kBufferStateReading);
-
-  GError* error = nullptr;
-  CHECK(canceller_[index]);
-  cancelled_ = g_cancellable_is_cancelled(canceller_[index]) == TRUE;
-
-  ssize_t bytes_read = g_input_stream_read_finish(src_stream_, res, &error);
-  if (bytes_read < 0) {
-    LOG(ERROR) << "Read failed: " << utils::GetAndFreeGError(&error);
-    failed_ = true;
-    buffer_state_[index] = kBufferStateEmpty;
-  } else if (bytes_read == 0) {
-    read_done_ = true;
-    buffer_state_[index] = kBufferStateEmpty;
-  } else {
-    buffer_valid_size_[index] = bytes_read;
-    buffer_state_[index] = kBufferStateFull;
-    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;
-    }
-  }
-}
-
-void FilesystemCopierAction::StaticAsyncReadReadyCallback(
-    GObject *source_object,
-    GAsyncResult *res,
-    gpointer user_data) {
-  reinterpret_cast<FilesystemCopierAction*>(user_data)->
-      AsyncReadReadyCallback(source_object, res);
-}
-
-void FilesystemCopierAction::AsyncWriteReadyCallback(GObject *source_object,
-                                                     GAsyncResult *res) {
-  int index = buffer_state_[0] == kBufferStateWriting ? 0 : 1;
-  CHECK(buffer_state_[index] == kBufferStateWriting);
-  buffer_state_[index] = kBufferStateEmpty;
-
-  GError* error = nullptr;
-  CHECK(canceller_[index]);
-  cancelled_ = g_cancellable_is_cancelled(canceller_[index]) == TRUE;
-
-  ssize_t bytes_written = g_output_stream_write_finish(dst_stream_,
-                                                       res,
-                                                       &error);
-
-  if (bytes_written < static_cast<ssize_t>(buffer_valid_size_[index])) {
-    if (bytes_written < 0) {
-      LOG(ERROR) << "Write error: " << utils::GetAndFreeGError(&error);
-    } else {
-      LOG(ERROR) << "Wrote too few bytes: " << bytes_written
-                 << " < " << buffer_valid_size_[index];
-    }
-    failed_ = true;
-  }
-
-  SpawnAsyncActions();
-}
-
-void FilesystemCopierAction::StaticAsyncWriteReadyCallback(
-    GObject *source_object,
-    GAsyncResult *res,
-    gpointer user_data) {
-  reinterpret_cast<FilesystemCopierAction*>(user_data)->
-      AsyncWriteReadyCallback(source_object, res);
-}
-
-void FilesystemCopierAction::SpawnAsyncActions() {
-  bool reading = false;
-  bool writing = false;
-  for (int i = 0; i < 2; i++) {
-    if (buffer_state_[i] == kBufferStateReading) {
-      reading = true;
-    }
-    if (buffer_state_[i] == kBufferStateWriting) {
-      writing = true;
-    }
-  }
-  if (failed_ || cancelled_) {
-    if (!reading && !writing) {
-      Cleanup(ErrorCode::kError);
-    }
-    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(),
-          bytes_to_read,
-          G_PRIORITY_DEFAULT,
-          canceller_[i],
-          &FilesystemCopierAction::StaticAsyncReadReadyCallback,
-          this);
-      reading = true;
-      buffer_state_[i] = kBufferStateReading;
-    } else if (!writing && !verify_hash_ &&
-               buffer_state_[i] == kBufferStateFull) {
-      g_output_stream_write_async(
-          dst_stream_,
-          buffer_[i].data(),
-          buffer_valid_size_[i],
-          G_PRIORITY_DEFAULT,
-          canceller_[i],
-          &FilesystemCopierAction::StaticAsyncWriteReadyCallback,
-          this);
-      writing = true;
-      buffer_state_[i] = kBufferStateWriting;
-    }
-  }
-  if (!reading && !writing) {
-    // We're done!
-    ErrorCode code = ErrorCode::kSuccess;
-    if (hasher_.Finalize()) {
-      LOG(INFO) << "Hash: " << hasher_.hash();
-      if (verify_hash_) {
-        if (copying_kernel_install_path_) {
-          if (install_plan_.kernel_hash != hasher_.raw_hash()) {
-            code = ErrorCode::kNewKernelVerificationError;
-            LOG(ERROR) << "New kernel verification failed.";
-          }
-        } else {
-          if (install_plan_.rootfs_hash != hasher_.raw_hash()) {
-            code = ErrorCode::kNewRootfsVerificationError;
-            LOG(ERROR) << "New rootfs verification failed.";
-          }
-        }
-      } else {
-        if (copying_kernel_install_path_) {
-          install_plan_.kernel_hash = hasher_.raw_hash();
-        } else {
-          install_plan_.rootfs_hash = hasher_.raw_hash();
-        }
-      }
-    } else {
-      LOG(ERROR) << "Unable to finalize the hash.";
-      code = ErrorCode::kError;
-    }
-    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_ &&
-      utils::GetFilesystemSizeFromFD(fd, &block_count, &block_size)) {
-    filesystem_size_ = static_cast<int64_t>(block_count) * block_size;
-    LOG(INFO) << "Filesystem size: " << filesystem_size_ << " bytes ("
-              << block_count << "x" << block_size << ").";
-  }
-}
-
-}  // namespace chromeos_update_engine
diff --git a/filesystem_copier_action.h b/filesystem_copier_action.h
deleted file mode 100644
index b0544b0..0000000
--- a/filesystem_copier_action.h
+++ /dev/null
@@ -1,144 +0,0 @@
-// Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef UPDATE_ENGINE_FILESYSTEM_COPIER_ACTION_H_
-#define UPDATE_ENGINE_FILESYSTEM_COPIER_ACTION_H_
-
-#include <sys/stat.h>
-#include <sys/types.h>
-
-#include <string>
-#include <vector>
-
-#include <gio/gio.h>
-#include <glib.h>
-#include <gtest/gtest_prod.h>  // for FRIEND_TEST
-
-#include "update_engine/action.h"
-#include "update_engine/install_plan.h"
-#include "update_engine/omaha_hash_calculator.h"
-
-// This action will only do real work if it's a delta update. It will
-// copy the root partition to install partition, and then terminate.
-
-namespace chromeos_update_engine {
-
-class SystemState;
-
-class FilesystemCopierAction : public InstallPlanAction {
- public:
-  FilesystemCopierAction(SystemState* system_state,
-                         bool copying_kernel_install_path,
-                         bool verify_hash);
-
-  void PerformAction() override;
-  void TerminateProcessing() override;
-
-  // Used for testing. Return true if Cleanup() has not yet been called due
-  // to a callback upon the completion or cancellation of the copier action.
-  // A test should wait until IsCleanupPending() returns false before
-  // terminating the glib main loop.
-  bool IsCleanupPending() const;
-
-  // Used for testing, so we can copy from somewhere other than root
-  void set_copy_source(const std::string& path) { copy_source_ = path; }
-
-  // Debugging/logging
-  static std::string StaticType() { return "FilesystemCopierAction"; }
-  std::string Type() const override { return StaticType(); }
-
- private:
-  friend class FilesystemCopierActionTest;
-  FRIEND_TEST(FilesystemCopierActionTest, RunAsRootDetermineFilesystemSizeTest);
-
-  // Ping-pong buffers generally cycle through the following states:
-  // Empty->Reading->Full->Writing->Empty. In hash verification mode the state
-  // is never set to Writing.
-  enum BufferState {
-    kBufferStateEmpty,
-    kBufferStateReading,
-    kBufferStateFull,
-    kBufferStateWriting
-  };
-
-  // Callbacks from glib when the read/write operation is done.
-  void AsyncReadReadyCallback(GObject *source_object, GAsyncResult *res);
-  static void StaticAsyncReadReadyCallback(GObject *source_object,
-                                           GAsyncResult *res,
-                                           gpointer user_data);
-
-  void AsyncWriteReadyCallback(GObject *source_object, GAsyncResult *res);
-  static void StaticAsyncWriteReadyCallback(GObject *source_object,
-                                            GAsyncResult *res,
-                                            gpointer user_data);
-
-  // Based on the state of the ping-pong buffers spawns appropriate read/write
-  // actions asynchronously.
-  void SpawnAsyncActions();
-
-  // Cleans up all the variables we use for async operations and tells the
-  // ActionProcessor we're done w/ |code| as passed in. |cancelled_| should be
-  // true if TerminateProcessing() was called.
-  void Cleanup(ErrorCode 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);
-
-  // 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_;
-
-  // If non-null, these are GUnixInputStream objects for the opened
-  // source/destination partitions.
-  GInputStream* src_stream_;
-  GOutputStream* dst_stream_;
-
-  // Ping-pong buffers for storing data we read/write. Only one buffer is being
-  // read at a time and only one buffer is being written at a time.
-  chromeos::Blob buffer_[2];
-
-  // The state of each buffer.
-  BufferState buffer_state_[2];
-
-  // Number of valid elements in |buffer_| if its state is kBufferStateFull.
-  chromeos::Blob::size_type buffer_valid_size_[2];
-
-  // The cancellable objects for the in-flight async calls.
-  GCancellable* canceller_[2];
-
-  bool read_done_;  // true if reached EOF on the input stream.
-  bool failed_;  // true if the action has failed.
-  bool cancelled_;  // true if the action has been cancelled.
-
-  // The install plan we're passed in via the input pipe.
-  InstallPlan install_plan_;
-
-  // Calculates the hash of the copied data.
-  OmahaHashCalculator hasher_;
-
-  // Copies and hashes this many bytes from the head of the input stream. This
-  // field is initialized when the action is started and decremented as more
-  // bytes get copied.
-  int64_t filesystem_size_;
-
-  // The global context for update_engine.
-  SystemState* system_state_;
-
-  DISALLOW_COPY_AND_ASSIGN(FilesystemCopierAction);
-};
-
-}  // namespace chromeos_update_engine
-
-#endif  // UPDATE_ENGINE_FILESYSTEM_COPIER_ACTION_H_
diff --git a/filesystem_copier_action_unittest.cc b/filesystem_copier_action_unittest.cc
deleted file mode 100644
index 1aa1065..0000000
--- a/filesystem_copier_action_unittest.cc
+++ /dev/null
@@ -1,434 +0,0 @@
-// Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "update_engine/filesystem_copier_action.h"
-
-#include <fcntl.h>
-
-#include <set>
-#include <string>
-#include <vector>
-
-#include <base/posix/eintr_wrapper.h>
-#include <base/strings/string_util.h>
-#include <base/strings/stringprintf.h>
-#include <glib.h>
-#include <gmock/gmock.h>
-#include <gtest/gtest.h>
-
-#include "update_engine/fake_system_state.h"
-#include "update_engine/mock_hardware.h"
-#include "update_engine/omaha_hash_calculator.h"
-#include "update_engine/test_utils.h"
-#include "update_engine/utils.h"
-
-using std::set;
-using std::string;
-using std::vector;
-
-namespace chromeos_update_engine {
-
-class FilesystemCopierActionTest : public ::testing::Test {
- protected:
-  // |verify_hash|: 0 - no hash verification, 1 -- successful hash verification,
-  // 2 -- hash verification failure.
-  // Returns true iff test has completed successfully.
-  bool DoTest(bool run_out_of_space,
-              bool terminate_early,
-              bool use_kernel_partition,
-              int verify_hash);
-
-  FakeSystemState fake_system_state_;
-};
-
-class FilesystemCopierActionTestDelegate : public ActionProcessorDelegate {
- public:
-  FilesystemCopierActionTestDelegate(GMainLoop* loop,
-                                     FilesystemCopierAction* action)
-      : loop_(loop), action_(action), ran_(false), code_(ErrorCode::kError) {}
-  void ExitMainLoop() {
-    GMainContext* context = g_main_loop_get_context(loop_);
-    // We cannot use g_main_context_pending() alone to determine if it is safe
-    // to quit the main loop here because g_main_context_pending() may return
-    // FALSE when g_input_stream_read_async() in FilesystemCopierAction has
-    // been cancelled but the callback has not yet been invoked.
-    while (g_main_context_pending(context) || action_->IsCleanupPending()) {
-      g_main_context_iteration(context, false);
-      g_usleep(100);
-    }
-    g_main_loop_quit(loop_);
-  }
-  void ProcessingDone(const ActionProcessor* processor, ErrorCode code) {
-    ExitMainLoop();
-  }
-  void ProcessingStopped(const ActionProcessor* processor) {
-    ExitMainLoop();
-  }
-  void ActionCompleted(ActionProcessor* processor,
-                       AbstractAction* action,
-                       ErrorCode code) {
-    if (action->Type() == FilesystemCopierAction::StaticType()) {
-      ran_ = true;
-      code_ = code;
-    }
-  }
-  bool ran() const { return ran_; }
-  ErrorCode code() const { return code_; }
-
- private:
-  GMainLoop* loop_;
-  FilesystemCopierAction* action_;
-  bool ran_;
-  ErrorCode code_;
-};
-
-struct StartProcessorCallbackArgs {
-  ActionProcessor* processor;
-  FilesystemCopierAction* filesystem_copier_action;
-  bool terminate_early;
-};
-
-gboolean StartProcessorInRunLoop(gpointer data) {
-  StartProcessorCallbackArgs* args =
-      reinterpret_cast<StartProcessorCallbackArgs*>(data);
-  ActionProcessor* processor = args->processor;
-  processor->StartProcessing();
-  if (args->terminate_early) {
-    EXPECT_TRUE(args->filesystem_copier_action);
-    args->processor->StopProcessing();
-  }
-  return FALSE;
-}
-
-// TODO(garnold) Temporarily disabling this test, see chromium-os:31082 for
-// details; still trying to track down the root cause for these rare write
-// failures and whether or not they are due to the test setup or an inherent
-// issue with the chroot environment, library versions we use, etc.
-TEST_F(FilesystemCopierActionTest, DISABLED_RunAsRootSimpleTest) {
-  ASSERT_EQ(0, getuid());
-  bool test = DoTest(false, false, true, 0);
-  EXPECT_TRUE(test);
-  if (!test)
-    return;
-  test = DoTest(false, false, false, 0);
-  EXPECT_TRUE(test);
-}
-
-bool FilesystemCopierActionTest::DoTest(bool run_out_of_space,
-                                        bool terminate_early,
-                                        bool use_kernel_partition,
-                                        int verify_hash) {
-  // We need MockHardware to verify MarkUnbootable calls, but don't want
-  // warnings about other usages.
-  testing::NiceMock<MockHardware> mock_hardware;
-  fake_system_state_.set_hardware(&mock_hardware);
-
-  GMainLoop *loop = g_main_loop_new(g_main_context_default(), FALSE);
-
-  string a_loop_file;
-  string b_loop_file;
-
-  if (!(utils::MakeTempFile("a_loop_file.XXXXXX", &a_loop_file, nullptr) &&
-        utils::MakeTempFile("b_loop_file.XXXXXX", &b_loop_file, nullptr))) {
-    ADD_FAILURE();
-    return false;
-  }
-  ScopedPathUnlinker a_loop_file_unlinker(a_loop_file);
-  ScopedPathUnlinker b_loop_file_unlinker(b_loop_file);
-
-  // Make random data for a, zero filled data for b.
-  const size_t kLoopFileSize = 10 * 1024 * 1024 + 512;
-  chromeos::Blob a_loop_data(kLoopFileSize);
-  test_utils::FillWithData(&a_loop_data);
-  chromeos::Blob b_loop_data(run_out_of_space ?
-                             (kLoopFileSize - 1) :
-                             kLoopFileSize,
-                             0);  // Fill with 0s
-
-  // Write data to disk
-  if (!(test_utils::WriteFileVector(a_loop_file, a_loop_data) &&
-        test_utils::WriteFileVector(b_loop_file, b_loop_data))) {
-    ADD_FAILURE();
-    return false;
-  }
-
-  // Attach loop devices to the files
-  string a_dev;
-  string b_dev;
-
-  test_utils::ScopedLoopbackDeviceBinder a_dev_releaser(a_loop_file, &a_dev);
-  test_utils::ScopedLoopbackDeviceBinder b_dev_releaser(b_loop_file, &b_dev);
-  if (!(a_dev_releaser.is_bound() && b_dev_releaser.is_bound())) {
-    ADD_FAILURE();
-    return false;
-  }
-
-  LOG(INFO) << "copying: "
-            << a_loop_file << " (" << a_dev << ") -> "
-            << b_loop_file << " (" << b_dev << ", "
-            << kLoopFileSize << " bytes";
-  bool success = true;
-
-  // Set up the action objects
-  InstallPlan install_plan;
-  if (verify_hash) {
-    if (use_kernel_partition) {
-      install_plan.kernel_install_path = a_dev;
-      install_plan.kernel_size =
-          kLoopFileSize - ((verify_hash == 2) ? 1 : 0);
-      if (!OmahaHashCalculator::RawHashOfData(a_loop_data,
-                                              &install_plan.kernel_hash)) {
-        ADD_FAILURE();
-        success = false;
-      }
-    } else {
-      install_plan.install_path = a_dev;
-      install_plan.rootfs_size =
-          kLoopFileSize - ((verify_hash == 2) ? 1 : 0);
-      if (!OmahaHashCalculator::RawHashOfData(a_loop_data,
-                                              &install_plan.rootfs_hash)) {
-        ADD_FAILURE();
-        success = false;
-      }
-    }
-  } else {
-    if (use_kernel_partition) {
-      install_plan.kernel_install_path = b_dev;
-    } else {
-      install_plan.install_path = b_dev;
-    }
-  }
-
-  EXPECT_CALL(mock_hardware,
-              MarkKernelUnbootable(a_dev)).Times(use_kernel_partition ? 1 : 0);
-
-  ActionProcessor processor;
-
-  ObjectFeederAction<InstallPlan> feeder_action;
-  FilesystemCopierAction copier_action(&fake_system_state_,
-                                       use_kernel_partition,
-                                       verify_hash != 0);
-  ObjectCollectorAction<InstallPlan> collector_action;
-
-  BondActions(&feeder_action, &copier_action);
-  BondActions(&copier_action, &collector_action);
-
-  FilesystemCopierActionTestDelegate delegate(loop, &copier_action);
-  processor.set_delegate(&delegate);
-  processor.EnqueueAction(&feeder_action);
-  processor.EnqueueAction(&copier_action);
-  processor.EnqueueAction(&collector_action);
-
-  if (!verify_hash) {
-    copier_action.set_copy_source(a_dev);
-  }
-  feeder_action.set_obj(install_plan);
-
-  StartProcessorCallbackArgs start_callback_args;
-  start_callback_args.processor = &processor;
-  start_callback_args.filesystem_copier_action = &copier_action;
-  start_callback_args.terminate_early = terminate_early;
-
-  g_timeout_add(0, &StartProcessorInRunLoop, &start_callback_args);
-  g_main_loop_run(loop);
-  g_main_loop_unref(loop);
-
-  if (!terminate_early) {
-    bool is_delegate_ran = delegate.ran();
-    EXPECT_TRUE(is_delegate_ran);
-    success = success && is_delegate_ran;
-  }
-  if (run_out_of_space || terminate_early) {
-    EXPECT_EQ(ErrorCode::kError, delegate.code());
-    return (ErrorCode::kError == delegate.code());
-  }
-  if (verify_hash == 2) {
-    ErrorCode expected_exit_code =
-        (use_kernel_partition ?
-         ErrorCode::kNewKernelVerificationError :
-         ErrorCode::kNewRootfsVerificationError);
-    EXPECT_EQ(expected_exit_code, delegate.code());
-    return (expected_exit_code == delegate.code());
-  }
-  EXPECT_EQ(ErrorCode::kSuccess, delegate.code());
-
-  // Make sure everything in the out_image is there
-  chromeos::Blob a_out;
-  if (!utils::ReadFile(a_dev, &a_out)) {
-    ADD_FAILURE();
-    return false;
-  }
-  const bool is_a_file_reading_eq =
-      test_utils::ExpectVectorsEq(a_loop_data, a_out);
-  EXPECT_TRUE(is_a_file_reading_eq);
-  success = success && is_a_file_reading_eq;
-  if (!verify_hash) {
-    chromeos::Blob b_out;
-    if (!utils::ReadFile(b_dev, &b_out)) {
-      ADD_FAILURE();
-      return false;
-    }
-    const bool is_b_file_reading_eq = test_utils::ExpectVectorsEq(a_out, b_out);
-    EXPECT_TRUE(is_b_file_reading_eq);
-    success = success && is_b_file_reading_eq;
-  }
-
-  bool is_install_plan_eq = (collector_action.object() == install_plan);
-  EXPECT_TRUE(is_install_plan_eq);
-  success = success && is_install_plan_eq;
-
-  LOG(INFO) << "Verifying bootable flag on: " << a_dev;
-  bool bootable;
-  EXPECT_TRUE(mock_hardware.fake().IsKernelBootable(a_dev, &bootable));
-  // We should always mark a partition as unbootable if it's a kernel
-  // partition, but never if it's anything else.
-  EXPECT_EQ(bootable, !use_kernel_partition);
-
-  return success;
-}
-
-class FilesystemCopierActionTest2Delegate : public ActionProcessorDelegate {
- public:
-  void ActionCompleted(ActionProcessor* processor,
-                       AbstractAction* action,
-                       ErrorCode code) {
-    if (action->Type() == FilesystemCopierAction::StaticType()) {
-      ran_ = true;
-      code_ = code;
-    }
-  }
-  GMainLoop *loop_;
-  bool ran_;
-  ErrorCode code_;
-};
-
-TEST_F(FilesystemCopierActionTest, MissingInputObjectTest) {
-  ActionProcessor processor;
-  FilesystemCopierActionTest2Delegate delegate;
-
-  processor.set_delegate(&delegate);
-
-  FilesystemCopierAction copier_action(&fake_system_state_, false, false);
-  ObjectCollectorAction<InstallPlan> collector_action;
-
-  BondActions(&copier_action, &collector_action);
-
-  processor.EnqueueAction(&copier_action);
-  processor.EnqueueAction(&collector_action);
-  processor.StartProcessing();
-  EXPECT_FALSE(processor.IsRunning());
-  EXPECT_TRUE(delegate.ran_);
-  EXPECT_EQ(ErrorCode::kError, delegate.code_);
-}
-
-TEST_F(FilesystemCopierActionTest, ResumeTest) {
-  ActionProcessor processor;
-  FilesystemCopierActionTest2Delegate delegate;
-
-  processor.set_delegate(&delegate);
-
-  ObjectFeederAction<InstallPlan> feeder_action;
-  const char* kUrl = "http://some/url";
-  InstallPlan install_plan(false, true, kUrl, 0, "", 0, "", "", "", "", "", "");
-  feeder_action.set_obj(install_plan);
-  FilesystemCopierAction copier_action(&fake_system_state_, false, false);
-  ObjectCollectorAction<InstallPlan> collector_action;
-
-  BondActions(&feeder_action, &copier_action);
-  BondActions(&copier_action, &collector_action);
-
-  processor.EnqueueAction(&feeder_action);
-  processor.EnqueueAction(&copier_action);
-  processor.EnqueueAction(&collector_action);
-  processor.StartProcessing();
-  EXPECT_FALSE(processor.IsRunning());
-  EXPECT_TRUE(delegate.ran_);
-  EXPECT_EQ(ErrorCode::kSuccess, delegate.code_);
-  EXPECT_EQ(kUrl, collector_action.object().download_url);
-}
-
-TEST_F(FilesystemCopierActionTest, NonExistentDriveTest) {
-  ActionProcessor processor;
-  FilesystemCopierActionTest2Delegate delegate;
-
-  processor.set_delegate(&delegate);
-
-  ObjectFeederAction<InstallPlan> feeder_action;
-  InstallPlan install_plan(false,
-                           false,
-                           "",
-                           0,
-                           "",
-                           0,
-                           "",
-                           "/no/such/file",
-                           "/no/such/file",
-                           "/no/such/file",
-                           "/no/such/file",
-                           "");
-  feeder_action.set_obj(install_plan);
-  FilesystemCopierAction copier_action(&fake_system_state_, false, false);
-  ObjectCollectorAction<InstallPlan> collector_action;
-
-  BondActions(&copier_action, &collector_action);
-
-  processor.EnqueueAction(&feeder_action);
-  processor.EnqueueAction(&copier_action);
-  processor.EnqueueAction(&collector_action);
-  processor.StartProcessing();
-  EXPECT_FALSE(processor.IsRunning());
-  EXPECT_TRUE(delegate.ran_);
-  EXPECT_EQ(ErrorCode::kError, delegate.code_);
-}
-
-TEST_F(FilesystemCopierActionTest, RunAsRootVerifyHashTest) {
-  ASSERT_EQ(0, getuid());
-  EXPECT_TRUE(DoTest(false, false, false, 1));
-  EXPECT_TRUE(DoTest(false, false, true, 1));
-}
-
-TEST_F(FilesystemCopierActionTest, RunAsRootVerifyHashFailTest) {
-  ASSERT_EQ(0, getuid());
-  EXPECT_TRUE(DoTest(false, false, false, 2));
-  EXPECT_TRUE(DoTest(false, false, true, 2));
-}
-
-TEST_F(FilesystemCopierActionTest, RunAsRootNoSpaceTest) {
-  ASSERT_EQ(0, getuid());
-  EXPECT_TRUE(DoTest(true, false, false, 0));
-}
-
-TEST_F(FilesystemCopierActionTest, RunAsRootTerminateEarlyTest) {
-  ASSERT_EQ(0, getuid());
-  EXPECT_TRUE(DoTest(false, true, false, 0));
-}
-
-TEST_F(FilesystemCopierActionTest, RunAsRootDetermineFilesystemSizeTest) {
-  string img;
-  EXPECT_TRUE(utils::MakeTempFile("img.XXXXXX", &img, nullptr));
-  ScopedPathUnlinker img_unlinker(img);
-  test_utils::CreateExtImageAtPath(img, nullptr);
-  // Extend the "partition" holding the file system from 10MiB to 20MiB.
-  EXPECT_EQ(0, test_utils::System(base::StringPrintf(
-      "dd if=/dev/zero of=%s seek=20971519 bs=1 count=1 status=none",
-      img.c_str())));
-  EXPECT_EQ(20 * 1024 * 1024, utils::FileSize(img));
-
-  for (int i = 0; i < 2; ++i) {
-    bool is_kernel = i == 1;
-    FilesystemCopierAction action(&fake_system_state_, is_kernel, false);
-    EXPECT_EQ(kint64max, action.filesystem_size_);
-    {
-      int fd = HANDLE_EINTR(open(img.c_str(), O_RDONLY));
-      EXPECT_GT(fd, 0);
-      ScopedFdCloser fd_closer(&fd);
-      action.DetermineFilesystemSize(fd);
-    }
-    EXPECT_EQ(is_kernel ? kint64max : 10 * 1024 * 1024,
-              action.filesystem_size_);
-  }
-}
-
-
-}  // namespace chromeos_update_engine
diff --git a/filesystem_verifier_action.cc b/filesystem_verifier_action.cc
new file mode 100644
index 0000000..8810763
--- /dev/null
+++ b/filesystem_verifier_action.cc
@@ -0,0 +1,260 @@
+// Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "update_engine/filesystem_verifier_action.h"
+
+#include <errno.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include <algorithm>
+#include <cstdlib>
+#include <string>
+
+#include <gio/gio.h>
+#include <gio/gunixinputstream.h>
+#include <glib.h>
+
+#include "update_engine/glib_utils.h"
+#include "update_engine/hardware_interface.h"
+#include "update_engine/subprocess.h"
+#include "update_engine/system_state.h"
+#include "update_engine/utils.h"
+
+using std::string;
+
+namespace chromeos_update_engine {
+
+namespace {
+const off_t kCopyFileBufferSize = 128 * 1024;
+}  // namespace
+
+FilesystemVerifierAction::FilesystemVerifierAction(
+    SystemState* system_state,
+    PartitionType partition_type)
+    : partition_type_(partition_type),
+      src_stream_(nullptr),
+      canceller_(nullptr),
+      read_done_(false),
+      failed_(false),
+      cancelled_(false),
+      remaining_size_(kint64max),
+      system_state_(system_state) {}
+
+void FilesystemVerifierAction::PerformAction() {
+  // Will tell the ActionProcessor we've failed if we return.
+  ScopedActionCompleter abort_action_completer(processor_, this);
+
+  if (!HasInputObject()) {
+    LOG(ERROR) << "FilesystemVerifierAction missing input object.";
+    return;
+  }
+  install_plan_ = GetInputObject();
+
+  if (partition_type_ == PartitionType::kKernel) {
+    LOG(INFO) << "verifying kernel, marking as unbootable";
+    if (!system_state_->hardware()->MarkKernelUnbootable(
+        install_plan_.kernel_install_path)) {
+      PLOG(ERROR) << "Unable to clear kernel GPT boot flags: " <<
+          install_plan_.kernel_install_path;
+    }
+  }
+
+  if (install_plan_.is_full_update &&
+      (partition_type_ == PartitionType::kSourceRootfs ||
+       partition_type_ == PartitionType::kSourceKernel)) {
+    // No hash verification needed. Done!
+    LOG(INFO) << "filesystem verifying skipped on full update.";
+    if (HasOutputPipe())
+      SetOutputObject(install_plan_);
+    abort_action_completer.set_code(ErrorCode::kSuccess);
+    return;
+  }
+
+  string target_path;
+  switch (partition_type_) {
+    case PartitionType::kRootfs:
+      target_path = install_plan_.install_path;
+      if (target_path.empty()) {
+        utils::GetInstallDev(system_state_->hardware()->BootDevice(),
+                             &target_path);
+      }
+      break;
+    case PartitionType::kKernel:
+      target_path = install_plan_.kernel_install_path;
+      if (target_path.empty()) {
+        string rootfs_path;
+        utils::GetInstallDev(system_state_->hardware()->BootDevice(),
+                             &rootfs_path);
+        target_path = utils::KernelDeviceOfBootDevice(rootfs_path);
+      }
+      break;
+    case PartitionType::kSourceRootfs:
+      target_path = install_plan_.source_path.empty() ?
+                    system_state_->hardware()->BootDevice() :
+                    install_plan_.source_path;
+      break;
+    case PartitionType::kSourceKernel:
+      target_path = install_plan_.kernel_source_path.empty() ?
+                    utils::KernelDeviceOfBootDevice(
+                        system_state_->hardware()->BootDevice()) :
+                    install_plan_.kernel_source_path;
+      break;
+  }
+
+  int src_fd = open(target_path.c_str(), O_RDONLY);
+  if (src_fd < 0) {
+    PLOG(ERROR) << "Unable to open " << target_path << " for reading:";
+    return;
+  }
+
+  DetermineFilesystemSize(src_fd);
+  src_stream_ = g_unix_input_stream_new(src_fd, TRUE);
+
+  buffer_.resize(kCopyFileBufferSize);
+  canceller_ = g_cancellable_new();
+
+  // Start the first read.
+  SpawnAsyncActions();
+
+  abort_action_completer.set_should_complete(false);
+}
+
+void FilesystemVerifierAction::TerminateProcessing() {
+  if (canceller_) {
+    g_cancellable_cancel(canceller_);
+  }
+}
+
+bool FilesystemVerifierAction::IsCleanupPending() const {
+  return (src_stream_ != nullptr);
+}
+
+void FilesystemVerifierAction::Cleanup(ErrorCode code) {
+  g_object_unref(canceller_);
+  canceller_ = nullptr;
+  g_object_unref(src_stream_);
+  src_stream_ = nullptr;
+  if (cancelled_)
+    return;
+  if (code == ErrorCode::kSuccess && HasOutputPipe())
+    SetOutputObject(install_plan_);
+  processor_->ActionComplete(this, code);
+}
+
+void FilesystemVerifierAction::AsyncReadReadyCallback(GObject *source_object,
+                                                      GAsyncResult *res) {
+  GError* error = nullptr;
+  CHECK(canceller_);
+  cancelled_ = g_cancellable_is_cancelled(canceller_) == TRUE;
+
+  ssize_t bytes_read = g_input_stream_read_finish(src_stream_, res, &error);
+
+  if (bytes_read < 0) {
+    LOG(ERROR) << "Read failed: " << utils::GetAndFreeGError(&error);
+    failed_ = true;
+  } else if (bytes_read == 0) {
+    read_done_ = true;
+  } else {
+    remaining_size_ -= bytes_read;
+  }
+
+  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_.data(), bytes_read)) {
+      LOG(ERROR) << "Unable to update the hash.";
+      failed_ = true;
+    }
+  }
+  SpawnAsyncActions();
+}
+
+void FilesystemVerifierAction::StaticAsyncReadReadyCallback(
+    GObject *source_object,
+    GAsyncResult *res,
+    gpointer user_data) {
+  reinterpret_cast<FilesystemVerifierAction*>(user_data)->
+      AsyncReadReadyCallback(source_object, res);
+}
+
+void FilesystemVerifierAction::SpawnAsyncActions() {
+  if (failed_ || cancelled_) {
+    Cleanup(ErrorCode::kError);
+    return;
+  }
+
+  if (!read_done_) {
+    int64_t bytes_to_read = std::min(static_cast<int64_t>(buffer_.size()),
+                                     remaining_size_);
+    g_input_stream_read_async(
+        src_stream_,
+        buffer_.data(),
+        bytes_to_read,
+        G_PRIORITY_DEFAULT,
+        canceller_,
+        &FilesystemVerifierAction::StaticAsyncReadReadyCallback,
+        this);
+  } else {
+    // We're done!
+    ErrorCode code = ErrorCode::kSuccess;
+    if (hasher_.Finalize()) {
+      LOG(INFO) << "Hash: " << hasher_.hash();
+      switch (partition_type_) {
+        case PartitionType::kRootfs:
+          if (install_plan_.rootfs_hash != hasher_.raw_hash()) {
+            code = ErrorCode::kNewRootfsVerificationError;
+            LOG(ERROR) << "New rootfs verification failed.";
+          }
+          break;
+        case PartitionType::kKernel:
+          if (install_plan_.kernel_hash != hasher_.raw_hash()) {
+            code = ErrorCode::kNewKernelVerificationError;
+            LOG(ERROR) << "New kernel verification failed.";
+          }
+          break;
+        case PartitionType::kSourceRootfs:
+          install_plan_.source_rootfs_hash = hasher_.raw_hash();
+          break;
+        case PartitionType::kSourceKernel:
+          install_plan_.source_kernel_hash = hasher_.raw_hash();
+          break;
+      }
+    } else {
+      LOG(ERROR) << "Unable to finalize the hash.";
+      code = ErrorCode::kError;
+    }
+    Cleanup(code);
+  }
+}
+
+void FilesystemVerifierAction::DetermineFilesystemSize(int fd) {
+  switch (partition_type_) {
+    case PartitionType::kRootfs:
+      remaining_size_ = install_plan_.rootfs_size;
+      LOG(INFO) << "Filesystem size: " << remaining_size_ << " bytes.";
+      break;
+    case PartitionType::kKernel:
+      remaining_size_ = install_plan_.kernel_size;
+      LOG(INFO) << "Filesystem size: " << remaining_size_ << " bytes.";
+      break;
+    case PartitionType::kSourceRootfs:
+      {
+        int block_count = 0, block_size = 0;
+        if (utils::GetFilesystemSizeFromFD(fd, &block_count, &block_size)) {
+          remaining_size_ = static_cast<int64_t>(block_count) * block_size;
+          LOG(INFO) << "Filesystem size: " << remaining_size_ << " bytes ("
+                    << block_count << "x" << block_size << ").";
+        }
+      }
+      break;
+    default:
+      break;
+  }
+  return;
+}
+
+}  // namespace chromeos_update_engine
diff --git a/filesystem_verifier_action.h b/filesystem_verifier_action.h
new file mode 100644
index 0000000..f934ab4
--- /dev/null
+++ b/filesystem_verifier_action.h
@@ -0,0 +1,116 @@
+// Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UPDATE_ENGINE_FILESYSTEM_VERIFIER_ACTION_H_
+#define UPDATE_ENGINE_FILESYSTEM_VERIFIER_ACTION_H_
+
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include <string>
+#include <vector>
+
+#include <gio/gio.h>
+#include <glib.h>
+#include <gtest/gtest_prod.h>  // for FRIEND_TEST
+
+#include "update_engine/action.h"
+#include "update_engine/install_plan.h"
+#include "update_engine/omaha_hash_calculator.h"
+
+// This action will only do real work if it's a delta update. It will
+// copy the root partition to install partition, and then terminate.
+
+namespace chromeos_update_engine {
+
+class SystemState;
+
+// The type of filesystem that we are verifying.
+enum class PartitionType {
+  kSourceRootfs,
+  kSourceKernel,
+  kRootfs,
+  kKernel,
+};
+
+class FilesystemVerifierAction : public InstallPlanAction {
+ public:
+  FilesystemVerifierAction(SystemState* system_state,
+                           PartitionType partition_type);
+
+  void PerformAction() override;
+  void TerminateProcessing() override;
+
+  // Used for testing. Return true if Cleanup() has not yet been called due
+  // to a callback upon the completion or cancellation of the verifier action.
+  // A test should wait until IsCleanupPending() returns false before
+  // terminating the glib main loop.
+  bool IsCleanupPending() const;
+
+  // Debugging/logging
+  static std::string StaticType() { return "FilesystemVerifierAction"; }
+  std::string Type() const override { return StaticType(); }
+
+ private:
+  friend class FilesystemVerifierActionTest;
+  FRIEND_TEST(FilesystemVerifierActionTest,
+              RunAsRootDetermineFilesystemSizeTest);
+
+  // Callbacks from glib when the read operation is done.
+  void AsyncReadReadyCallback(GObject *source_object, GAsyncResult *res);
+  static void StaticAsyncReadReadyCallback(GObject *source_object,
+                                           GAsyncResult *res,
+                                           gpointer user_data);
+
+  // Based on the state of the ping-pong buffers spawns appropriate read
+  // actions asynchronously.
+  void SpawnAsyncActions();
+
+  // Cleans up all the variables we use for async operations and tells the
+  // ActionProcessor we're done w/ |code| as passed in. |cancelled_| should be
+  // true if TerminateProcessing() was called.
+  void Cleanup(ErrorCode 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);
+
+  // The type of the partition that we are verifying.
+  PartitionType partition_type_;
+
+  // If non-null, this is the GUnixInputStream object for the opened source
+  // partition.
+  GInputStream* src_stream_;
+
+  // Buffer for storing data we read.
+  chromeos::Blob buffer_;
+
+  // The cancellable object for the in-flight async calls.
+  GCancellable* canceller_;
+
+  bool read_done_;  // true if reached EOF on the input stream.
+  bool failed_;  // true if the action has failed.
+  bool cancelled_;  // true if the action has been cancelled.
+
+  // The install plan we're passed in via the input pipe.
+  InstallPlan install_plan_;
+
+  // Calculates the hash of the data.
+  OmahaHashCalculator hasher_;
+
+  // Reads and hashes this many bytes from the head of the input stream. This
+  // field is initialized when the action is started and decremented as more
+  // bytes get read.
+  int64_t remaining_size_;
+
+  // The global context for update_engine.
+  SystemState* system_state_;
+
+  DISALLOW_COPY_AND_ASSIGN(FilesystemVerifierAction);
+};
+
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_FILESYSTEM_VERIFIER_ACTION_H_
diff --git a/filesystem_verifier_action_unittest.cc b/filesystem_verifier_action_unittest.cc
new file mode 100644
index 0000000..a1842e8
--- /dev/null
+++ b/filesystem_verifier_action_unittest.cc
@@ -0,0 +1,386 @@
+// Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "update_engine/filesystem_verifier_action.h"
+
+#include <fcntl.h>
+
+#include <set>
+#include <string>
+#include <vector>
+
+#include <base/posix/eintr_wrapper.h>
+#include <base/strings/string_util.h>
+#include <base/strings/stringprintf.h>
+#include <glib.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+#include "update_engine/fake_system_state.h"
+#include "update_engine/mock_hardware.h"
+#include "update_engine/omaha_hash_calculator.h"
+#include "update_engine/test_utils.h"
+#include "update_engine/utils.h"
+
+using std::set;
+using std::string;
+using std::vector;
+
+namespace chromeos_update_engine {
+
+class FilesystemVerifierActionTest : public ::testing::Test {
+ protected:
+  // Returns true iff test has completed successfully.
+  bool DoTest(bool terminate_early,
+              bool hash_fail,
+              PartitionType partition_type);
+
+  FakeSystemState fake_system_state_;
+};
+
+class FilesystemVerifierActionTestDelegate : public ActionProcessorDelegate {
+ public:
+  FilesystemVerifierActionTestDelegate(GMainLoop* loop,
+                                       FilesystemVerifierAction* action)
+      : loop_(loop), action_(action), ran_(false), code_(ErrorCode::kError) {}
+  void ExitMainLoop() {
+    GMainContext* context = g_main_loop_get_context(loop_);
+    // We cannot use g_main_context_pending() alone to determine if it is safe
+    // to quit the main loop here because g_main_context_pending() may return
+    // FALSE when g_input_stream_read_async() in FilesystemVerifierAction has
+    // been cancelled but the callback has not yet been invoked.
+    while (g_main_context_pending(context) || action_->IsCleanupPending()) {
+      g_main_context_iteration(context, false);
+      g_usleep(100);
+    }
+    g_main_loop_quit(loop_);
+  }
+  void ProcessingDone(const ActionProcessor* processor, ErrorCode code) {
+    ExitMainLoop();
+  }
+  void ProcessingStopped(const ActionProcessor* processor) {
+    ExitMainLoop();
+  }
+  void ActionCompleted(ActionProcessor* processor,
+                       AbstractAction* action,
+                       ErrorCode code) {
+    if (action->Type() == FilesystemVerifierAction::StaticType()) {
+      ran_ = true;
+      code_ = code;
+    }
+  }
+  bool ran() const { return ran_; }
+  ErrorCode code() const { return code_; }
+
+ private:
+  GMainLoop* loop_;
+  FilesystemVerifierAction* action_;
+  bool ran_;
+  ErrorCode code_;
+};
+
+struct StartProcessorCallbackArgs {
+  ActionProcessor* processor;
+  FilesystemVerifierAction* filesystem_copier_action;
+  bool terminate_early;
+};
+
+gboolean StartProcessorInRunLoop(gpointer data) {
+  StartProcessorCallbackArgs* args =
+      reinterpret_cast<StartProcessorCallbackArgs*>(data);
+  ActionProcessor* processor = args->processor;
+  processor->StartProcessing();
+  if (args->terminate_early) {
+    EXPECT_TRUE(args->filesystem_copier_action);
+    args->processor->StopProcessing();
+  }
+  return FALSE;
+}
+
+// TODO(garnold) Temporarily disabling this test, see chromium-os:31082 for
+// details; still trying to track down the root cause for these rare write
+// failures and whether or not they are due to the test setup or an inherent
+// issue with the chroot environment, library versions we use, etc.
+TEST_F(FilesystemVerifierActionTest, DISABLED_RunAsRootSimpleTest) {
+  ASSERT_EQ(0, getuid());
+  bool test = DoTest(false, false, PartitionType::kKernel);
+  EXPECT_TRUE(test);
+  if (!test)
+    return;
+  test = DoTest(false, false, PartitionType::kRootfs);
+  EXPECT_TRUE(test);
+}
+
+bool FilesystemVerifierActionTest::DoTest(bool terminate_early,
+                                          bool hash_fail,
+                                          PartitionType partition_type) {
+  // We need MockHardware to verify MarkUnbootable calls, but don't want
+  // warnings about other usages.
+  testing::NiceMock<MockHardware> mock_hardware;
+  fake_system_state_.set_hardware(&mock_hardware);
+
+  GMainLoop *loop = g_main_loop_new(g_main_context_default(), FALSE);
+
+  string a_loop_file;
+
+  if (!(utils::MakeTempFile("a_loop_file.XXXXXX", &a_loop_file, nullptr))) {
+    ADD_FAILURE();
+    return false;
+  }
+  ScopedPathUnlinker a_loop_file_unlinker(a_loop_file);
+
+  // Make random data for a, zero filled data for b.
+  const size_t kLoopFileSize = 10 * 1024 * 1024 + 512;
+  chromeos::Blob a_loop_data(kLoopFileSize);
+  test_utils::FillWithData(&a_loop_data);
+
+
+  // Write data to disk
+  if (!(test_utils::WriteFileVector(a_loop_file, a_loop_data))) {
+    ADD_FAILURE();
+    return false;
+  }
+
+  // Attach loop devices to the files
+  string a_dev;
+  test_utils::ScopedLoopbackDeviceBinder a_dev_releaser(a_loop_file, &a_dev);
+  if (!(a_dev_releaser.is_bound())) {
+    ADD_FAILURE();
+    return false;
+  }
+
+  LOG(INFO) << "verifying: "  << a_loop_file << " (" << a_dev << ")";
+
+  bool success = true;
+
+  // Set up the action objects
+  InstallPlan install_plan;
+  switch (partition_type) {
+    case PartitionType::kRootfs:
+      install_plan.rootfs_size = kLoopFileSize - (hash_fail ? 1 : 0);
+      install_plan.install_path = a_dev;
+      if (!OmahaHashCalculator::RawHashOfData(
+          a_loop_data, &install_plan.rootfs_hash)) {
+        ADD_FAILURE();
+        success = false;
+      }
+      break;
+    case PartitionType::kKernel:
+      install_plan.kernel_size = kLoopFileSize - (hash_fail ? 1 : 0);
+      install_plan.kernel_install_path = a_dev;
+      if (!OmahaHashCalculator::RawHashOfData(
+          a_loop_data, &install_plan.kernel_hash)) {
+        ADD_FAILURE();
+        success = false;
+      }
+      break;
+    case PartitionType::kSourceRootfs:
+      install_plan.source_path = a_dev;
+      if (!OmahaHashCalculator::RawHashOfData(
+          a_loop_data, &install_plan.source_rootfs_hash)) {
+        ADD_FAILURE();
+        success = false;
+      }
+      break;
+    case PartitionType::kSourceKernel:
+      install_plan.kernel_source_path = a_dev;
+      if (!OmahaHashCalculator::RawHashOfData(
+          a_loop_data, &install_plan.source_kernel_hash)) {
+        ADD_FAILURE();
+        success = false;
+      }
+      break;
+  }
+
+  EXPECT_CALL(mock_hardware,
+              MarkKernelUnbootable(a_dev)).Times(
+                  partition_type == PartitionType::kKernel ? 1 : 0);
+
+  ActionProcessor processor;
+
+  ObjectFeederAction<InstallPlan> feeder_action;
+  FilesystemVerifierAction copier_action(&fake_system_state_, partition_type);
+  ObjectCollectorAction<InstallPlan> collector_action;
+
+  BondActions(&feeder_action, &copier_action);
+  BondActions(&copier_action, &collector_action);
+
+  FilesystemVerifierActionTestDelegate delegate(loop, &copier_action);
+  processor.set_delegate(&delegate);
+  processor.EnqueueAction(&feeder_action);
+  processor.EnqueueAction(&copier_action);
+  processor.EnqueueAction(&collector_action);
+
+  feeder_action.set_obj(install_plan);
+
+  StartProcessorCallbackArgs start_callback_args;
+  start_callback_args.processor = &processor;
+  start_callback_args.filesystem_copier_action = &copier_action;
+  start_callback_args.terminate_early = terminate_early;
+
+  g_timeout_add(0, &StartProcessorInRunLoop, &start_callback_args);
+  g_main_loop_run(loop);
+  g_main_loop_unref(loop);
+
+  if (!terminate_early) {
+    bool is_delegate_ran = delegate.ran();
+    EXPECT_TRUE(is_delegate_ran);
+    success = success && is_delegate_ran;
+  } else {
+    EXPECT_EQ(ErrorCode::kError, delegate.code());
+    return (ErrorCode::kError == delegate.code());
+  }
+  if (hash_fail) {
+    ErrorCode expected_exit_code =
+        ((partition_type == PartitionType::kKernel ||
+          partition_type == PartitionType::kSourceKernel) ?
+         ErrorCode::kNewKernelVerificationError :
+         ErrorCode::kNewRootfsVerificationError);
+    EXPECT_EQ(expected_exit_code, delegate.code());
+    return (expected_exit_code == delegate.code());
+  }
+  EXPECT_EQ(ErrorCode::kSuccess, delegate.code());
+
+  // Make sure everything in the out_image is there
+  chromeos::Blob a_out;
+  if (!utils::ReadFile(a_dev, &a_out)) {
+    ADD_FAILURE();
+    return false;
+  }
+  const bool is_a_file_reading_eq =
+      test_utils::ExpectVectorsEq(a_loop_data, a_out);
+  EXPECT_TRUE(is_a_file_reading_eq);
+  success = success && is_a_file_reading_eq;
+
+  bool is_install_plan_eq = (collector_action.object() == install_plan);
+  EXPECT_TRUE(is_install_plan_eq);
+  success = success && is_install_plan_eq;
+
+  LOG(INFO) << "Verifying bootable flag on: " << a_dev;
+  bool bootable;
+  EXPECT_TRUE(mock_hardware.fake().IsKernelBootable(a_dev, &bootable));
+  // We should always mark a partition as unbootable if it's a kernel
+  // partition, but never if it's anything else.
+  EXPECT_EQ(bootable, (partition_type != PartitionType::kKernel));
+
+  return success;
+}
+
+class FilesystemVerifierActionTest2Delegate : public ActionProcessorDelegate {
+ public:
+  void ActionCompleted(ActionProcessor* processor,
+                       AbstractAction* action,
+                       ErrorCode code) {
+    if (action->Type() == FilesystemVerifierAction::StaticType()) {
+      ran_ = true;
+      code_ = code;
+    }
+  }
+  GMainLoop *loop_;
+  bool ran_;
+  ErrorCode code_;
+};
+
+TEST_F(FilesystemVerifierActionTest, MissingInputObjectTest) {
+  ActionProcessor processor;
+  FilesystemVerifierActionTest2Delegate delegate;
+
+  processor.set_delegate(&delegate);
+
+  FilesystemVerifierAction copier_action(&fake_system_state_,
+                                         PartitionType::kRootfs);
+  ObjectCollectorAction<InstallPlan> collector_action;
+
+  BondActions(&copier_action, &collector_action);
+
+  processor.EnqueueAction(&copier_action);
+  processor.EnqueueAction(&collector_action);
+  processor.StartProcessing();
+  EXPECT_FALSE(processor.IsRunning());
+  EXPECT_TRUE(delegate.ran_);
+  EXPECT_EQ(ErrorCode::kError, delegate.code_);
+}
+
+TEST_F(FilesystemVerifierActionTest, NonExistentDriveTest) {
+  ActionProcessor processor;
+  FilesystemVerifierActionTest2Delegate delegate;
+
+  processor.set_delegate(&delegate);
+
+  ObjectFeederAction<InstallPlan> feeder_action;
+  InstallPlan install_plan(false,
+                           false,
+                           "",
+                           0,
+                           "",
+                           0,
+                           "",
+                           "/no/such/file",
+                           "/no/such/file",
+                           "/no/such/file",
+                           "/no/such/file",
+                           "");
+  feeder_action.set_obj(install_plan);
+  FilesystemVerifierAction verifier_action(&fake_system_state_,
+                                           PartitionType::kRootfs);
+  ObjectCollectorAction<InstallPlan> collector_action;
+
+  BondActions(&verifier_action, &collector_action);
+
+  processor.EnqueueAction(&feeder_action);
+  processor.EnqueueAction(&verifier_action);
+  processor.EnqueueAction(&collector_action);
+  processor.StartProcessing();
+  EXPECT_FALSE(processor.IsRunning());
+  EXPECT_TRUE(delegate.ran_);
+  EXPECT_EQ(ErrorCode::kError, delegate.code_);
+}
+
+TEST_F(FilesystemVerifierActionTest, RunAsRootVerifyHashTest) {
+  ASSERT_EQ(0, getuid());
+  EXPECT_TRUE(DoTest(false, false, PartitionType::kRootfs));
+  EXPECT_TRUE(DoTest(false, false, PartitionType::kKernel));
+  EXPECT_TRUE(DoTest(false, false, PartitionType::kSourceRootfs));
+  EXPECT_TRUE(DoTest(false, false, PartitionType::kSourceKernel));
+}
+
+TEST_F(FilesystemVerifierActionTest, RunAsRootVerifyHashFailTest) {
+  ASSERT_EQ(0, getuid());
+  EXPECT_TRUE(DoTest(false, true, PartitionType::kRootfs));
+  EXPECT_TRUE(DoTest(false, true, PartitionType::kKernel));
+}
+
+TEST_F(FilesystemVerifierActionTest, RunAsRootTerminateEarlyTest) {
+  ASSERT_EQ(0, getuid());
+  EXPECT_TRUE(DoTest(true, false, PartitionType::kKernel));
+}
+
+TEST_F(FilesystemVerifierActionTest, RunAsRootDetermineFilesystemSizeTest) {
+  string img;
+  EXPECT_TRUE(utils::MakeTempFile("img.XXXXXX", &img, nullptr));
+  ScopedPathUnlinker img_unlinker(img);
+  test_utils::CreateExtImageAtPath(img, nullptr);
+  // Extend the "partition" holding the file system from 10MiB to 20MiB.
+  EXPECT_EQ(0, test_utils::System(base::StringPrintf(
+      "dd if=/dev/zero of=%s seek=20971519 bs=1 count=1 status=none",
+      img.c_str())));
+  EXPECT_EQ(20 * 1024 * 1024, utils::FileSize(img));
+
+  for (int i = 0; i < 2; ++i) {
+    PartitionType fs_type =
+        i ? PartitionType::kSourceKernel : PartitionType::kSourceRootfs;
+    FilesystemVerifierAction action(&fake_system_state_, fs_type);
+    EXPECT_EQ(kint64max, action.remaining_size_);
+    {
+      int fd = HANDLE_EINTR(open(img.c_str(), O_RDONLY));
+      EXPECT_GT(fd, 0);
+      ScopedFdCloser fd_closer(&fd);
+      action.DetermineFilesystemSize(fd);
+    }
+    EXPECT_EQ(i ? kint64max : 10 * 1024 * 1024,
+              action.remaining_size_);
+  }
+}
+
+}  // namespace chromeos_update_engine
diff --git a/install_plan.h b/install_plan.h
index 1772a47..756822a 100644
--- a/install_plan.h
+++ b/install_plan.h
@@ -56,19 +56,21 @@
 
   // 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.
+  // 1. FilesystemVerifierAction 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.
+  // 3. FilesystemVerifierAction computes and verifies the applied and source
+  // partition sizes and hashes against the expected values.
   uint64_t kernel_size;
   uint64_t rootfs_size;
   chromeos::Blob kernel_hash;
   chromeos::Blob rootfs_hash;
+  chromeos::Blob source_kernel_hash;
+  chromeos::Blob source_rootfs_hash;
 
   // True if payload hash checks are mandatory based on the system state and
   // the Omaha response.
diff --git a/payload_state.cc b/payload_state.cc
index 87965d7..5ed219e 100644
--- a/payload_state.cc
+++ b/payload_state.cc
@@ -329,6 +329,7 @@
     case ErrorCode::kPostinstallPowerwashError:
     case ErrorCode::kUpdateCanceledByChannelChange:
     case ErrorCode::kOmahaRequestXMLHasEntityDecl:
+    case ErrorCode::kFilesystemVerifierError:
       LOG(INFO) << "Not incrementing URL index or failure count for this error";
       break;
 
diff --git a/payload_state_unittest.cc b/payload_state_unittest.cc
index c449d4a..08ed398 100644
--- a/payload_state_unittest.cc
+++ b/payload_state_unittest.cc
@@ -92,7 +92,7 @@
 class PayloadStateTest : public ::testing::Test { };
 
 TEST(PayloadStateTest, DidYouAddANewErrorCode) {
-  if (static_cast<int>(ErrorCode::kUmaReportedMax) != 47) {
+  if (static_cast<int>(ErrorCode::kUmaReportedMax) != 48) {
     LOG(ERROR) << "The following failure is intentional. If you added a new "
                << "ErrorCode enum value, make sure to add it to the "
                << "PayloadState::UpdateFailed method and then update this test "
diff --git a/update_attempter.cc b/update_attempter.cc
index 139aaf2..678f38c 100644
--- a/update_attempter.cc
+++ b/update_attempter.cc
@@ -32,7 +32,7 @@
 #include "update_engine/dbus_service.h"
 #include "update_engine/dbus_wrapper_interface.h"
 #include "update_engine/download_action.h"
-#include "update_engine/filesystem_copier_action.h"
+#include "update_engine/filesystem_verifier_action.h"
 #include "update_engine/glib_utils.h"
 #include "update_engine/hardware_interface.h"
 #include "update_engine/libcurl_http_fetcher.h"
@@ -110,7 +110,7 @@
 }
 
 // Turns a generic ErrorCode::kError to a generic error code specific
-// to |action| (e.g., ErrorCode::kFilesystemCopierError). If |code| is
+// to |action| (e.g., ErrorCode::kFilesystemVerifierError). If |code| is
 // not ErrorCode::kError, or the action is not matched, returns |code|
 // unchanged.
 ErrorCode GetErrorCodeForAction(AbstractAction* action,
@@ -123,8 +123,8 @@
     return ErrorCode::kOmahaRequestError;
   if (type == OmahaResponseHandlerAction::StaticType())
     return ErrorCode::kOmahaResponseHandlerError;
-  if (type == FilesystemCopierAction::StaticType())
-    return ErrorCode::kFilesystemCopierError;
+  if (type == FilesystemVerifierAction::StaticType())
+    return ErrorCode::kFilesystemVerifierError;
   if (type == PostinstallRunnerAction::StaticType())
     return ErrorCode::kPostinstallRunnerError;
 
@@ -604,11 +604,12 @@
                              false));
   shared_ptr<OmahaResponseHandlerAction> response_handler_action(
       new OmahaResponseHandlerAction(system_state_));
-  // We start with the kernel so it's marked as invalid more quickly.
-  shared_ptr<FilesystemCopierAction> kernel_filesystem_copier_action(
-      new FilesystemCopierAction(system_state_, true, false));
-  shared_ptr<FilesystemCopierAction> filesystem_copier_action(
-      new FilesystemCopierAction(system_state_, false, false));
+  shared_ptr<FilesystemVerifierAction> src_filesystem_verifier_action(
+      new FilesystemVerifierAction(system_state_,
+                                   PartitionType::kSourceRootfs));
+  shared_ptr<FilesystemVerifierAction> src_kernel_filesystem_verifier_action(
+      new FilesystemVerifierAction(system_state_,
+                                   PartitionType::kSourceKernel));
 
   shared_ptr<OmahaRequestAction> download_started_action(
       new OmahaRequestAction(system_state_,
@@ -632,10 +633,10 @@
                              new LibcurlHttpFetcher(GetProxyResolver(),
                                                     system_state_),
                              false));
-  shared_ptr<FilesystemCopierAction> filesystem_verifier_action(
-      new FilesystemCopierAction(system_state_, false, true));
-  shared_ptr<FilesystemCopierAction> kernel_filesystem_verifier_action(
-      new FilesystemCopierAction(system_state_, true, true));
+  shared_ptr<FilesystemVerifierAction> dst_filesystem_verifier_action(
+      new FilesystemVerifierAction(system_state_, PartitionType::kRootfs));
+  shared_ptr<FilesystemVerifierAction> dst_kernel_filesystem_verifier_action(
+      new FilesystemVerifierAction(system_state_, PartitionType::kKernel));
   shared_ptr<OmahaRequestAction> update_complete_action(
       new OmahaRequestAction(system_state_,
                              new OmahaEvent(OmahaEvent::kTypeUpdateComplete),
@@ -649,32 +650,34 @@
 
   actions_.push_back(shared_ptr<AbstractAction>(update_check_action));
   actions_.push_back(shared_ptr<AbstractAction>(response_handler_action));
-  actions_.push_back(shared_ptr<AbstractAction>(filesystem_copier_action));
   actions_.push_back(shared_ptr<AbstractAction>(
-      kernel_filesystem_copier_action));
+      src_filesystem_verifier_action));
+  actions_.push_back(shared_ptr<AbstractAction>(
+      src_kernel_filesystem_verifier_action));
   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>(
+      dst_filesystem_verifier_action));
+  actions_.push_back(shared_ptr<AbstractAction>(
+      dst_kernel_filesystem_verifier_action));
 
   // Bond them together. We have to use the leaf-types when calling
   // BondActions().
   BondActions(update_check_action.get(),
               response_handler_action.get());
   BondActions(response_handler_action.get(),
-              filesystem_copier_action.get());
-  BondActions(filesystem_copier_action.get(),
-              kernel_filesystem_copier_action.get());
-  BondActions(kernel_filesystem_copier_action.get(),
+              src_filesystem_verifier_action.get());
+  BondActions(src_filesystem_verifier_action.get(),
+              src_kernel_filesystem_verifier_action.get());
+  BondActions(src_kernel_filesystem_verifier_action.get(),
               download_action.get());
   BondActions(download_action.get(),
-              filesystem_verifier_action.get());
-  BondActions(filesystem_verifier_action.get(),
-              kernel_filesystem_verifier_action.get());
+              dst_filesystem_verifier_action.get());
+  BondActions(dst_filesystem_verifier_action.get(),
+              dst_kernel_filesystem_verifier_action.get());
 
-  BuildPostInstallActions(kernel_filesystem_verifier_action.get());
+  BuildPostInstallActions(dst_kernel_filesystem_verifier_action.get());
 
   actions_.push_back(shared_ptr<AbstractAction>(update_complete_action));
 
diff --git a/update_attempter.h b/update_attempter.h
index 7bdcab4..c3495da 100644
--- a/update_attempter.h
+++ b/update_attempter.h
@@ -20,7 +20,6 @@
 #include "update_engine/action_processor.h"
 #include "update_engine/chrome_browser_proxy_resolver.h"
 #include "update_engine/download_action.h"
-#include "update_engine/filesystem_copier_action.h"
 #include "update_engine/omaha_request_params.h"
 #include "update_engine/omaha_response_handler_action.h"
 #include "update_engine/proxy_resolver.h"
diff --git a/update_attempter_unittest.cc b/update_attempter_unittest.cc
index 5d62cbe..6ef1d7a 100644
--- a/update_attempter_unittest.cc
+++ b/update_attempter_unittest.cc
@@ -17,7 +17,7 @@
 #include "update_engine/fake_clock.h"
 #include "update_engine/fake_prefs.h"
 #include "update_engine/fake_system_state.h"
-#include "update_engine/filesystem_copier_action.h"
+#include "update_engine/filesystem_verifier_action.h"
 #include "update_engine/install_plan.h"
 #include "update_engine/mock_action.h"
 #include "update_engine/mock_action_processor.h"
@@ -299,10 +299,10 @@
   EXPECT_EQ(ErrorCode::kOmahaResponseHandlerError,
             GetErrorCodeForAction(&omaha_response_handler_action,
                                   ErrorCode::kError));
-  FilesystemCopierAction filesystem_copier_action(
-      &fake_system_state_, false, false);
-  EXPECT_EQ(ErrorCode::kFilesystemCopierError,
-            GetErrorCodeForAction(&filesystem_copier_action,
+  FilesystemVerifierAction filesystem_verifier_action(
+      &fake_system_state_, PartitionType::kRootfs);
+  EXPECT_EQ(ErrorCode::kFilesystemVerifierError,
+            GetErrorCodeForAction(&filesystem_verifier_action,
                                   ErrorCode::kError));
   PostinstallRunnerAction postinstall_runner_action;
   EXPECT_EQ(ErrorCode::kPostinstallRunnerError,
@@ -459,13 +459,13 @@
 const string kUpdateActionTypes[] = {  // NOLINT(runtime/string)
   OmahaRequestAction::StaticType(),
   OmahaResponseHandlerAction::StaticType(),
-  FilesystemCopierAction::StaticType(),
-  FilesystemCopierAction::StaticType(),
+  FilesystemVerifierAction::StaticType(),
+  FilesystemVerifierAction::StaticType(),
   OmahaRequestAction::StaticType(),
   DownloadAction::StaticType(),
   OmahaRequestAction::StaticType(),
-  FilesystemCopierAction::StaticType(),
-  FilesystemCopierAction::StaticType(),
+  FilesystemVerifierAction::StaticType(),
+  FilesystemVerifierAction::StaticType(),
   PostinstallRunnerAction::StaticType(),
   OmahaRequestAction::StaticType()
 };
diff --git a/update_engine.gyp b/update_engine.gyp
index 8ac39ec..0a1a445 100644
--- a/update_engine.gyp
+++ b/update_engine.gyp
@@ -147,7 +147,7 @@
         'extent_writer.cc',
         'file_descriptor.cc',
         'file_writer.cc',
-        'filesystem_copier_action.cc',
+        'filesystem_verifier_action.cc',
         'glib_utils.cc',
         'hardware.cc',
         'http_common.cc',
@@ -364,7 +364,7 @@
             'fake_prefs.cc',
             'fake_system_state.cc',
             'file_writer_unittest.cc',
-            'filesystem_copier_action_unittest.cc',
+            'filesystem_verifier_action_unittest.cc',
             'http_fetcher_unittest.cc',
             'hwid_override_unittest.cc',
             'mock_http_fetcher.cc',
diff --git a/update_manager/chromeos_policy.cc b/update_manager/chromeos_policy.cc
index a304276..5abf5c6 100644
--- a/update_manager/chromeos_policy.cc
+++ b/update_manager/chromeos_policy.cc
@@ -116,6 +116,7 @@
     case ErrorCode::kPostinstallPowerwashError:
     case ErrorCode::kUpdateCanceledByChannelChange:
     case ErrorCode::kOmahaRequestXMLHasEntityDecl:
+    case ErrorCode::kFilesystemVerifierError:
       LOG(INFO) << "Not changing URL index or failure count due to error "
                 << chromeos_update_engine::utils::CodeToString(err_code)
                 << " (" << static_cast<int>(err_code) << ")";
diff --git a/utils.cc b/utils.cc
index b1e903c..86d8915 100644
--- a/utils.cc
+++ b/utils.cc
@@ -1125,6 +1125,7 @@
     case ErrorCode::kKernelDeviceOpenError:
     case ErrorCode::kDownloadWriteError:
     case ErrorCode::kFilesystemCopierError:
+    case ErrorCode::kFilesystemVerifierError:
       return metrics::AttemptResult::kOperationExecutionError;
 
     case ErrorCode::kDownloadMetadataSignatureMismatch:
@@ -1262,6 +1263,7 @@
     case ErrorCode::kUnsupportedMajorPayloadVersion:
     case ErrorCode::kUnsupportedMinorPayloadVersion:
     case ErrorCode::kOmahaRequestXMLHasEntityDecl:
+    case ErrorCode::kFilesystemVerifierError:
       break;
 
     // Special flags. These can't happen (we mask them out above) but
@@ -1481,6 +1483,8 @@
       return "ErrorCode::kUnsupportedMinorPayloadVersion";
     case ErrorCode::kOmahaRequestXMLHasEntityDecl:
       return "ErrorCode::kOmahaRequestXMLHasEntityDecl";
+    case ErrorCode::kFilesystemVerifierError:
+      return "ErrorCode::kFilesystemVerifierError";
     // Don't add a default case to let the compiler warn about newly added
     // error codes which should be added here.
   }