AU: FilesystemCopierAction: copy bit-exactly

Review URL: http://codereview.chromium.org/1700018
diff --git a/SConstruct b/SConstruct
index 9598975..6855425 100644
--- a/SConstruct
+++ b/SConstruct
@@ -138,7 +138,8 @@
   if os.environ.has_key(key):
     env['ENV'][key] = os.environ[key]
 
-env.ParseConfig('pkg-config --cflags --libs glib-2.0 dbus-1 dbus-glib-1')
+env.ParseConfig('pkg-config --cflags --libs '
+                'dbus-1 dbus-glib-1 gio-2.0 gio-unix-2.0 glib-2.0')
 env.ProtocolBuffer('update_metadata.pb.cc', 'update_metadata.proto')
 
 env.DbusBindings('update_engine.dbusclient.h', 'update_engine.xml')
@@ -190,6 +191,7 @@
                             extent_mapper_unittest.cc
                             extent_writer_unittest.cc
                             file_writer_unittest.cc
+                            filesystem_copier_action_unittest.cc
                             filesystem_iterator_unittest.cc
                             graph_utils_unittest.cc
                             http_fetcher_unittest.cc
diff --git a/filesystem_copier_action.cc b/filesystem_copier_action.cc
old mode 100644
new mode 100755
index 2a361ee..18bab44
--- a/filesystem_copier_action.cc
+++ b/filesystem_copier_action.cc
@@ -1,4 +1,4 @@
-// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// Copyright (c) 2010 The Chromium Authors. All rights reserved.
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
@@ -12,6 +12,10 @@
 #include <map>
 #include <string>
 #include <vector>
+#include <gio/gio.h>
+#include <gio/gunixinputstream.h>
+#include <gio/gunixoutputstream.h>
+#include <glib.h>
 #include "update_engine/filesystem_iterator.h"
 #include "update_engine/subprocess.h"
 #include "update_engine/utils.h"
@@ -24,284 +28,144 @@
 namespace chromeos_update_engine {
 
 namespace {
-const char* kMountpointTemplate = "/tmp/au_dest_mnt.XXXXXX";
-const off_t kCopyFileBufferSize = 4 * 1024 * 1024;
-const char* kCopyExclusionPrefix = "/lost+found";
+const off_t kCopyFileBufferSize = 2 * 1024 * 1024;
 }  // namespace {}
 
 void FilesystemCopierAction::PerformAction() {
+  // Will tell the ActionProcessor we've failed if we return.
+  ScopedActionCompleter abort_action_completer(processor_, this);
+
   if (!HasInputObject()) {
-    LOG(ERROR) << "No input object. Aborting.";
-    processor_->ActionComplete(this, false);
+    LOG(ERROR) << "FilesystemCopierAction missing input object.";
     return;
   }
   install_plan_ = GetInputObject();
 
   if (install_plan_.is_full_update) {
-    // No copy needed.
-    processor_->ActionComplete(this, true);
+    // No copy needed. Done!
+    abort_action_completer.set_success(true);
     return;
   }
 
-  {
-    // Set up dest_path_
-    char *dest_path_temp = strdup(kMountpointTemplate);
-    CHECK(dest_path_temp);
-    CHECK_EQ(mkdtemp(dest_path_temp), dest_path_temp);
-    CHECK_NE(dest_path_temp[0], '\0');
-    dest_path_ = dest_path_temp;
-    free(dest_path_temp);
+  const string source =
+      copy_source_.empty() ? utils::BootDevice() : copy_source_;
+  LOG(INFO) << "Copying from " << source << " to "
+            << 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(install_plan_.install_path.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;
   }
 
-  // Make sure we're properly mounted
-  if (Mount(install_plan_.install_path, dest_path_)) {
-    bool done_early = false;
-    if (utils::FileExists(
-            (dest_path_ +
-             FilesystemCopierAction::kCompleteFilesystemMarker).c_str())) {
-      // We're done!
-      done_early = true;
-      skipped_copy_ = true;
-      if (HasOutputPipe())
-        SetOutputObject(install_plan_);
-    }
-    if (!Unmount(dest_path_)) {
-      LOG(ERROR) << "Unmount failed. Aborting.";
-      processor_->ActionComplete(this, false);
-      return;
-    }
-    if (done_early) {
-      CHECK(!is_mounted_);
-      if (rmdir(dest_path_.c_str()) != 0)
-        LOG(ERROR) << "Unable to remove " << dest_path_;
-      processor_->ActionComplete(this, true);
-      return;
-    }
-  }
-  LOG(ERROR) << "not mounted; spawning thread";
-  // If we get here, mount failed or we're not done yet. Reformat and copy.
-  CHECK_EQ(pthread_create(&helper_thread_, NULL, HelperThreadMainStatic, this),
-           0);
+  src_stream_ = g_unix_input_stream_new(src_fd, TRUE);
+  dst_stream_ = g_unix_output_stream_new(dst_fd, TRUE);
+
+  buffer_.resize(kCopyFileBufferSize);
+
+  // Set up the first read
+  canceller_ = g_cancellable_new();
+
+  g_input_stream_read_async(src_stream_,
+                            &buffer_[0],
+                            buffer_.size(),
+                            G_PRIORITY_DEFAULT,
+                            canceller_,
+                            &FilesystemCopierAction::StaticAsyncReadyCallback,
+                            this);
+  read_in_flight_ = true;
+
+  abort_action_completer.set_should_complete(false);
 }
 
 void FilesystemCopierAction::TerminateProcessing() {
-  if (is_mounted_) {
-    LOG(ERROR) << "Aborted processing, but left a filesystem mounted.";
+  if (canceller_) {
+    g_cancellable_cancel(canceller_);
   }
 }
 
-bool FilesystemCopierAction::Mount(const string& device,
-                                   const string& mountpoint) {
-  CHECK(!is_mounted_);
-  if(utils::MountFilesystem(device, mountpoint, 0))
-    is_mounted_ = true;
-  return is_mounted_;
-}
-
-bool FilesystemCopierAction::Unmount(const string& mountpoint) {
-  CHECK(is_mounted_);
-  if (utils::UnmountFilesystem(mountpoint))
-    is_mounted_ = false;
-  return !is_mounted_;
-}
-
-void* FilesystemCopierAction::HelperThreadMain() {
-  // First, format the drive
-  vector<string> cmd;
-  cmd.push_back("/sbin/mkfs.ext3");
-  cmd.push_back("-F");
-  cmd.push_back(install_plan_.install_path);
-  int return_code = 1;
-  bool success = Subprocess::SynchronousExec(cmd, &return_code);
-  if (return_code != 0) {
-    LOG(INFO) << "Format of " << install_plan_.install_path
-              << " failed. Exit code: " << return_code;
-    success = false;
-  }
-  if (success) {
-    if (!Mount(install_plan_.install_path, dest_path_)) {
-      LOG(ERROR) << "Mount failed. Aborting";
-      success = false;
-    }
-  }
-  if (success) {
-    success = CopySynchronously();
-  }
-  if (success) {
-    // Place our marker to avoid copies again in the future
-    int r = open((dest_path_ +
-                  FilesystemCopierAction::kCompleteFilesystemMarker).c_str(),
-                 O_CREAT | O_WRONLY, 0644);
-    if (r >= 0)
-      close(r);
-  }
-  // Unmount
-  if (!Unmount(dest_path_)) {
-    LOG(ERROR) << "Unmount failed. Aborting";
-    success = false;
-  }
-  if (HasOutputPipe())
+void FilesystemCopierAction::Cleanup(bool success, bool was_cancelled) {
+  g_object_unref(src_stream_);
+  src_stream_ = NULL;
+  g_object_unref(dst_stream_);
+  dst_stream_ = NULL;
+  if (was_cancelled)
+    return;
+  if (success && HasOutputPipe())
     SetOutputObject(install_plan_);
-
-  // Tell main thread that we're done
-  g_timeout_add(0, CollectThreadStatic, this);
-  return reinterpret_cast<void*>(success ? 0 : 1);
-}
-
-void FilesystemCopierAction::CollectThread() {
-  void *thread_ret_value = NULL;
-  CHECK_EQ(pthread_join(helper_thread_, &thread_ret_value), 0);
-  bool success = (thread_ret_value == 0);
-  CHECK(!is_mounted_);
-  if (rmdir(dest_path_.c_str()) != 0)
-    LOG(INFO) << "Unable to remove " << dest_path_;
-  LOG(INFO) << "FilesystemCopierAction done";
   processor_->ActionComplete(this, success);
 }
 
-bool FilesystemCopierAction::CreateDirSynchronously(const std::string& new_path,
-                                                    const struct stat& stbuf) {
-  int r = mkdir(new_path.c_str(), stbuf.st_mode);
-  TEST_AND_RETURN_FALSE_ERRNO(r == 0);
-  return true;
-}
+void FilesystemCopierAction::AsyncReadyCallback(GObject *source_object,
+                                                GAsyncResult *res) {
+  GError* error = NULL;
+  CHECK(canceller_);
+  bool was_cancelled = g_cancellable_is_cancelled(canceller_) == TRUE;
+  g_object_unref(canceller_);
+  canceller_ = NULL;
 
-bool FilesystemCopierAction::CopyFileSynchronously(const std::string& old_path,
-                                                   const std::string& new_path,
-                                                   const struct stat& stbuf) {
-  int fd_out = open(new_path.c_str(), O_CREAT | O_EXCL | O_WRONLY,
-                    stbuf.st_mode);
-  TEST_AND_RETURN_FALSE_ERRNO(fd_out >= 0);
-  ScopedFdCloser fd_out_closer(&fd_out);
-  int fd_in = open(old_path.c_str(), O_RDONLY, 0);
-  TEST_AND_RETURN_FALSE_ERRNO(fd_in >= 0);
-  ScopedFdCloser fd_in_closer(&fd_in);
-
-  vector<char> buf(min(kCopyFileBufferSize, stbuf.st_size));
-  off_t bytes_written = 0;
-  while (true) {
-    // Make sure we don't need to abort early:
-    TEST_AND_RETURN_FALSE(!g_atomic_int_get(&thread_should_exit_));
-
-    ssize_t read_size = read(fd_in, &buf[0], buf.size());
-    TEST_AND_RETURN_FALSE_ERRNO(read_size >= 0);
-    if (0 == read_size)  // EOF
-      break;
-
-    ssize_t write_size = 0;
-    while (write_size < read_size) {
-      ssize_t r = write(fd_out, &buf[write_size], read_size - write_size);
-      TEST_AND_RETURN_FALSE_ERRNO(r >= 0);
-      write_size += r;
+  if (read_in_flight_) {
+    ssize_t bytes_read = g_input_stream_read_finish(src_stream_, res, &error);
+    if (bytes_read < 0) {
+      LOG(ERROR) << "Read failed:" << utils::GetGErrorMessage(error);
+      Cleanup(false, was_cancelled);
+      return;
     }
-    CHECK_EQ(write_size, read_size);
-    bytes_written += write_size;
-    CHECK_LE(bytes_written, stbuf.st_size);
-    if (bytes_written == stbuf.st_size)
-      break;
+
+    if (bytes_read == 0) {
+      // We're done!
+      Cleanup(true, was_cancelled);
+      return;
+    }
+    // Kick off a write
+    read_in_flight_ = false;
+    buffer_valid_size_ = bytes_read;
+    canceller_ = g_cancellable_new();
+    g_output_stream_write_async(
+        dst_stream_,
+        &buffer_[0],
+        bytes_read,
+        G_PRIORITY_DEFAULT,
+        canceller_,
+        &FilesystemCopierAction::StaticAsyncReadyCallback,
+        this);
+    return;
   }
-  CHECK_EQ(bytes_written, stbuf.st_size);
-  return true;
-}
 
-bool FilesystemCopierAction::CreateHardLinkSynchronously(
-    const std::string& old_path,
-    const std::string& new_path) {
-  int r = link(old_path.c_str(), new_path.c_str());
-  TEST_AND_RETURN_FALSE_ERRNO(r == 0);
-  return true;
-}
-
-bool FilesystemCopierAction::CopySymlinkSynchronously(
-    const std::string& old_path,
-    const std::string& new_path,
-    const struct stat& stbuf) {
-  vector<char> buf(PATH_MAX + 1);
-  ssize_t r = readlink(old_path.c_str(), &buf[0], buf.size());
-  TEST_AND_RETURN_FALSE_ERRNO(r >= 0);
-  // Make sure we got the entire link
-  TEST_AND_RETURN_FALSE(static_cast<unsigned>(r) < buf.size());
-  buf[r] = '\0';
-  int rc = symlink(&buf[0], new_path.c_str());
-  TEST_AND_RETURN_FALSE_ERRNO(rc == 0);
-  return true;
-}
-
-bool FilesystemCopierAction::CreateNodeSynchronously(
-    const std::string& new_path,
-    const struct stat& stbuf) {
-  int r = mknod(new_path.c_str(), stbuf.st_mode, stbuf.st_rdev);
-  TEST_AND_RETURN_FALSE_ERRNO(r == 0);
-  return true;
-}
-
-// Returns true on success
-bool FilesystemCopierAction::CopySynchronously() {
-  // This map is a map from inode # to new_path.
-  map<ino_t, string> hard_links;
-  FilesystemIterator iter(copy_source_,
-                          utils::SetWithValue<string>(kCopyExclusionPrefix));
-  bool success = true;
-  for (; !g_atomic_int_get(&thread_should_exit_) &&
-           !iter.IsEnd(); iter.Increment()) {
-    const string old_path = iter.GetFullPath();
-    const string new_path = dest_path_ + iter.GetPartialPath();
-    LOG(INFO) << "copying " << old_path << " to " << new_path;
-    const struct stat stbuf = iter.GetStat();
-    success = false;
-
-    // Skip lost+found
-    CHECK_NE(kCopyExclusionPrefix, iter.GetPartialPath());
-
-    // Directories can't be hard-linked, so check for directories first
-    if (iter.GetPartialPath().empty()) {
-      // Root has an empty path.
-      // We don't need to create anything for the root, which is the first
-      // thing we get from the iterator.
-      success = true;
-    } else if (S_ISDIR(stbuf.st_mode)) {
-      success = CreateDirSynchronously(new_path, stbuf);
+  ssize_t bytes_written = g_output_stream_write_finish(dst_stream_,
+                                                       res,
+                                                       &error);
+  if (bytes_written < static_cast<ssize_t>(buffer_valid_size_)) {
+    if (bytes_written < 0) {
+      LOG(ERROR) << "Write failed:" << utils::GetGErrorMessage(error);
     } else {
-      if (stbuf.st_nlink > 1 &&
-          utils::MapContainsKey(hard_links, stbuf.st_ino)) {
-        success = CreateHardLinkSynchronously(hard_links[stbuf.st_ino],
-                                              new_path);
-      } else {
-        if (stbuf.st_nlink > 1)
-          hard_links[stbuf.st_ino] = new_path;
-        if (S_ISREG(stbuf.st_mode)) {
-          success = CopyFileSynchronously(old_path, new_path, stbuf);
-        } else if (S_ISLNK(stbuf.st_mode)) {
-          success = CopySymlinkSynchronously(old_path, new_path, stbuf);
-        } else if (S_ISFIFO(stbuf.st_mode) ||
-                   S_ISCHR(stbuf.st_mode) ||
-                   S_ISBLK(stbuf.st_mode) ||
-                   S_ISSOCK(stbuf.st_mode)) {
-          success = CreateNodeSynchronously(new_path, stbuf);
-        } else {
-          CHECK(false) << "Unable to copy file " << old_path << " with mode "
-                       << stbuf.st_mode;
-        }
-      }
+      LOG(ERROR) << "Write was short: wrote " << bytes_written
+                 << " but expected to write " << buffer_valid_size_;
     }
-    TEST_AND_RETURN_FALSE(success);
-
-    // chmod new file
-    if (!S_ISLNK(stbuf.st_mode)) {
-      int r = chmod(new_path.c_str(), stbuf.st_mode);
-      TEST_AND_RETURN_FALSE_ERRNO(r == 0);
-    }
-
-    // Set uid/gid.
-    int r = lchown(new_path.c_str(), stbuf.st_uid, stbuf.st_gid);
-    TEST_AND_RETURN_FALSE_ERRNO(r == 0);
+    Cleanup(false, was_cancelled);
+    return;
   }
-  TEST_AND_RETURN_FALSE(!iter.IsErr());
-  // Success!
-  return true;
-}
 
-const char* FilesystemCopierAction::kCompleteFilesystemMarker(
-    "/update_engine_copy_success");
+  // Kick off a read
+  read_in_flight_ = true;
+  canceller_ = g_cancellable_new();
+  g_input_stream_read_async(
+      src_stream_,
+      &buffer_[0],
+      buffer_.size(),
+      G_PRIORITY_DEFAULT,
+      canceller_,
+      &FilesystemCopierAction::StaticAsyncReadyCallback,
+      this);
+}
 
 }  // namespace chromeos_update_engine
diff --git a/filesystem_copier_action.h b/filesystem_copier_action.h
index 3a330ef..9e7f060 100644
--- a/filesystem_copier_action.h
+++ b/filesystem_copier_action.h
@@ -1,4 +1,4 @@
-// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// Copyright (c) 2010 The Chromium Authors. All rights reserved.
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
@@ -8,30 +8,14 @@
 #include <sys/stat.h>
 #include <sys/types.h>
 #include <string>
+#include <vector>
+#include <gio/gio.h>
 #include <glib.h>
 #include "update_engine/action.h"
 #include "update_engine/install_plan.h"
 
 // This action will only do real work if it's a delta update. It will
-// format the install partition as ext3/4, copy the root filesystem into it,
-// and then terminate.
-
-// Implementation notes: This action uses a helper thread, which seems to
-// violate the design decision to only have a single thread and use
-// asynchronous i/o. The issue is that (to the best of my knowledge),
-// there are no linux APIs to crawl a filesystem's metadata asynchronously.
-// The suggested way seems to be to open the raw device and parse the ext
-// filesystem. That's not a good approach for a number of reasons:
-// - ties us to ext filesystem
-// - although this wouldn't happen at the time of writing, it may not handle
-//   changes to the source fs during the copy as gracefully.
-// - requires us to have read-access to the source filesystem device, which
-//   may be a security issue.
-//
-// Having said this, using a helper thread is not ideal, but it's acceptable:
-// we still honor the Action API. That is, all interaction between the action
-// and other objects in the system (e.g. the ActionProcessor) happens on the
-// main thread. The helper thread is fully encapsulated by the action.
+// copy the root partition to install partition, and then terminate.
 
 namespace chromeos_update_engine {
 
@@ -49,10 +33,11 @@
 class FilesystemCopierAction : public Action<FilesystemCopierAction> {
  public:
   FilesystemCopierAction()
-      : thread_should_exit_(0),
-        is_mounted_(false),
-        copy_source_("/"),
-        skipped_copy_(false) {}
+      : src_stream_(NULL),
+        dst_stream_(NULL),
+        canceller_(NULL),
+        read_in_flight_(false),
+        buffer_valid_size_(0) {}
   typedef ActionTraits<FilesystemCopierAction>::InputObjectType
   InputObjectType;
   typedef ActionTraits<FilesystemCopierAction>::OutputObjectType
@@ -64,81 +49,51 @@
   void set_copy_source(const std::string& path) {
     copy_source_ = path;
   }
-  // Returns true if we detected that a copy was unneeded and thus skipped it.
-  bool skipped_copy() { return skipped_copy_; }
 
   // Debugging/logging
   static std::string StaticType() { return "FilesystemCopierAction"; }
   std::string Type() const { return StaticType(); }
 
  private:
-  // These synchronously mount or unmount the given mountpoint
-  bool Mount(const std::string& device, const std::string& mountpoint);
-  bool Unmount(const std::string& mountpoint);
-
-  // Performs a recursive file/directory copy from copy_source_ to dest_path_.
-  // Doesn't return until the copy has completed. Returns true on success
-  // or false on error.
-  bool CopySynchronously();
-
-  // There are helper functions for CopySynchronously. They handle creating
-  // various types of files. They return true on success.
-  bool CreateDirSynchronously(const std::string& new_path,
-                              const struct stat& stbuf);
-  bool CopyFileSynchronously(const std::string& old_path,
-                             const std::string& new_path,
-                             const struct stat& stbuf);
-  bool CreateHardLinkSynchronously(const std::string& old_path,
-                                   const std::string& new_path);
-  // Note: Here, old_path is an existing symlink that will be copied to
-  // new_path. Thus, old_path is *not* the same as the old_path from
-  // the symlink() syscall.
-  bool CopySymlinkSynchronously(const std::string& old_path,
-                                const std::string& new_path,
-                                const struct stat& stbuf);
-  bool CreateNodeSynchronously(const std::string& new_path,
-                               const struct stat& stbuf);
-
-  // Returns NULL on success
-  void* HelperThreadMain();
-  static void* HelperThreadMainStatic(void* data) {
-    FilesystemCopierAction* self =
-        reinterpret_cast<FilesystemCopierAction*>(data);
-    return self->HelperThreadMain();
+  // Callback from glib when the copy operation is done.
+  void AsyncReadyCallback(GObject *source_object, GAsyncResult *res);
+  static void StaticAsyncReadyCallback(GObject *source_object,
+                                       GAsyncResult *res,
+                                       gpointer user_data) {
+    reinterpret_cast<FilesystemCopierAction*>(user_data)->AsyncReadyCallback(
+        source_object, res);
   }
-
-  // Joins the thread and tells the processor that we're done
-  void CollectThread();
-  // GMainLoop callback function:
-  static gboolean CollectThreadStatic(gpointer data) {
-    FilesystemCopierAction* self =
-        reinterpret_cast<FilesystemCopierAction*>(data);
-    self->CollectThread();
-    return FALSE;
-  }
-
-  pthread_t helper_thread_;
-
-  volatile gint thread_should_exit_;
-
-  static const char* kCompleteFilesystemMarker;
-
-  // Whether or not the destination device is currently mounted.
-  bool is_mounted_;
-
-  // Where the destination device is mounted.
-  std::string dest_path_;
-
-  // The path to copy from. Usually left as the default "/", but tests can
-  // change it.
+  
+  // Cleans up all the variables we use for async operations and tells
+  // the ActionProcessor we're done w/ success as passed in.
+  // was_cancelled should be true if TerminateProcessing() was called.
+  void Cleanup(bool success, bool was_cancelled);
+  
+  // 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_;
+  
+  // If non-NULL, the cancellable object for the in-flight async call.
+  GCancellable* canceller_;
+  
+  // True if we're waiting on a read to complete; false if we're
+  // waiting on a write.
+  bool read_in_flight_;
+  
+  // The buffer for storing data we read/write.
+  std::vector<char> buffer_;
+
+  // Number of valid elements in buffer_.
+  std::vector<char>::size_type buffer_valid_size_;
+
   // The install plan we're passed in via the input pipe.
   InstallPlan install_plan_;
-
-  // Set to true if we detected the copy was unneeded and thus we skipped it.
-  bool skipped_copy_;
-
+  
   DISALLOW_COPY_AND_ASSIGN(FilesystemCopierAction);
 };
 
diff --git a/filesystem_copier_action_unittest.cc b/filesystem_copier_action_unittest.cc
index 86c15ac..3ccea2b 100644
--- a/filesystem_copier_action_unittest.cc
+++ b/filesystem_copier_action_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// Copyright (c) 2010 The Chromium Authors. All rights reserved.
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
@@ -7,6 +7,7 @@
 #include <string>
 #include <vector>
 #include <gtest/gtest.h>
+#include "base/string_util.h"
 #include "update_engine/filesystem_copier_action.h"
 #include "update_engine/filesystem_iterator.h"
 #include "update_engine/omaha_hash_calculator.h"
@@ -21,22 +22,28 @@
 
 class FilesystemCopierActionTest : public ::testing::Test {
  protected:
-  void DoTest(bool double_copy, bool run_out_of_space);
-  string TestDir() { return "./FilesystemCopierActionTestDir"; }
+  void DoTest(bool run_out_of_space, bool terminate_early);
   void SetUp() {
-    System(string("mkdir -p ") + TestDir());
   }
   void TearDown() {
-    System(string("rm -rf ") + TestDir());
   }
 };
 
 class FilesystemCopierActionTestDelegate : public ActionProcessorDelegate {
  public:
   FilesystemCopierActionTestDelegate() : ran_(false), success_(false) {}
-  void ProcessingDone(const ActionProcessor* processor, bool success) {
+  void ExitMainLoop() {
+    while (g_main_context_pending(NULL)) {
+      g_main_context_iteration(NULL, false);
+    }
     g_main_loop_quit(loop_);
   }
+  void ProcessingDone(const ActionProcessor* processor, bool success) {
+    ExitMainLoop();
+  }
+  void ProcessingStopped(const ActionProcessor* processor) {
+    ExitMainLoop();
+  }
   void ActionCompleted(ActionProcessor* processor,
                        AbstractAction* action,
                        bool success) {
@@ -56,9 +63,21 @@
   bool success_;
 };
 
+struct StartProcessorCallbackArgs {
+  ActionProcessor* processor;
+  FilesystemCopierAction* filesystem_copier_action;
+  bool terminate_early;
+};
+
 gboolean StartProcessorInRunLoop(gpointer data) {
-  ActionProcessor* processor = reinterpret_cast<ActionProcessor*>(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;
 }
 
@@ -66,44 +85,54 @@
   ASSERT_EQ(0, getuid());
   DoTest(false, false);
 }
-void FilesystemCopierActionTest::DoTest(bool double_copy,
-                                        bool run_out_of_space) {
+void FilesystemCopierActionTest::DoTest(bool run_out_of_space,
+                                        bool terminate_early) {
   GMainLoop *loop = g_main_loop_new(g_main_context_default(), FALSE);
 
-  // make two populated ext images, mount them both (one in the other),
-  // and copy to a loop device setup to correspond to another file.
+  string a_loop_file;
+  string b_loop_file;
+  
+  EXPECT_TRUE(utils::MakeTempFile("/tmp/a_loop_file.XXXXXX",
+                                  &a_loop_file,
+                                  NULL));
+  ScopedPathUnlinker a_loop_file_unlinker(a_loop_file);
+  EXPECT_TRUE(utils::MakeTempFile("/tmp/b_loop_file.XXXXXX",
+                                  &b_loop_file,
+                                  NULL));
+  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;
+  vector<char> a_loop_data(kLoopFileSize);
+  FillWithData(&a_loop_data);
+  vector<char> b_loop_data(run_out_of_space ?
+                           (kLoopFileSize - 1) :
+                           kLoopFileSize,
+                           '\0');  // Fill with 0s
 
-  const string a_image(TestDir() + "/a_image");
-  const string b_image(TestDir() + "/b_image");
-  const string out_image(TestDir() + "/out_image");
+  // Write data to disk
+  EXPECT_TRUE(WriteFileVector(a_loop_file, a_loop_data));
+  EXPECT_TRUE(WriteFileVector(b_loop_file, b_loop_data));
 
-  vector<string> expected_paths_vector;
-  CreateExtImageAtPath(a_image, &expected_paths_vector);
-  CreateExtImageAtPath(b_image, NULL);
+  // Make loop devices for the files
+  string a_dev = GetUnusedLoopDevice();
+  EXPECT_FALSE(a_dev.empty());
+  EXPECT_EQ(0, System(StringPrintf("losetup %s %s",
+                                   a_dev.c_str(),
+                                   a_loop_file.c_str())));
+  ScopedLoopbackDeviceReleaser a_dev_releaser(a_dev);
 
-  // create 5 MiB file
-  ASSERT_EQ(0, System(string("dd if=/dev/zero of=") + out_image
-                      + " seek=5242879 bs=1 count=1"));
+  string b_dev = GetUnusedLoopDevice();
+  EXPECT_FALSE(b_dev.empty());
+  EXPECT_EQ(0, System(StringPrintf("losetup %s %s",
+                                   b_dev.c_str(),
+                                   b_loop_file.c_str())));
+  ScopedLoopbackDeviceReleaser b_dev_releaser(b_dev);
 
-  // mount them both
-  System(("mkdir -p " + TestDir() + "/mnt").c_str());
-  ASSERT_EQ(0, System(string("mount -o loop ") + a_image + " " +
-                      TestDir() + "/mnt"));
-  ASSERT_EQ(0,
-            System(string("mount -o loop ") + b_image + " " +
-                   TestDir() + "/mnt/some_dir/mnt"));
-
-  if (run_out_of_space)
-    ASSERT_EQ(0, System(string("dd if=/dev/zero of=") +
-                        TestDir() + "/mnt/big_zero bs=5M count=1"));
-
-  string dev = GetUnusedLoopDevice();
-
-  EXPECT_EQ(0, System(string("losetup ") + dev + " " + out_image));
-
+  // Set up the action objects
   InstallPlan install_plan;
   install_plan.is_full_update = false;
-  install_plan.install_path = dev;
+  install_plan.install_path = b_dev;
 
   ActionProcessor processor;
   FilesystemCopierActionTestDelegate delegate;
@@ -112,72 +141,43 @@
 
   ObjectFeederAction<InstallPlan> feeder_action;
   FilesystemCopierAction copier_action;
-  FilesystemCopierAction copier_action2;
   ObjectCollectorAction<InstallPlan> collector_action;
 
   BondActions(&feeder_action, &copier_action);
-  if (double_copy) {
-    BondActions(&copier_action, &copier_action2);
-    BondActions(&copier_action2, &collector_action);
-  } else {
-    BondActions(&copier_action, &collector_action);
-  }
+  BondActions(&copier_action, &collector_action);
 
   processor.EnqueueAction(&feeder_action);
   processor.EnqueueAction(&copier_action);
-  if (double_copy)
-    processor.EnqueueAction(&copier_action2);
   processor.EnqueueAction(&collector_action);
 
-  copier_action.set_copy_source(TestDir() + "/mnt");
+  copier_action.set_copy_source(a_dev);
   feeder_action.set_obj(install_plan);
 
-  g_timeout_add(0, &StartProcessorInRunLoop, &processor);
+  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);
 
-  EXPECT_EQ(0, System(string("losetup -d ") + dev));
-  EXPECT_EQ(0, System(string("umount -d ") + TestDir() + "/mnt/some_dir/mnt"));
-  EXPECT_EQ(0, System(string("umount -d ") + TestDir() + "/mnt"));
-  EXPECT_EQ(0, unlink(a_image.c_str()));
-  EXPECT_EQ(0, unlink(b_image.c_str()));
-
-  EXPECT_TRUE(delegate.ran());
-  if (run_out_of_space) {
+  if (!terminate_early)
+    EXPECT_TRUE(delegate.ran());
+  if (run_out_of_space || terminate_early) {
     EXPECT_FALSE(delegate.success());
-    EXPECT_EQ(0, unlink(out_image.c_str()));
-    EXPECT_EQ(0, rmdir((TestDir() + "/mnt").c_str()));
     return;
   }
   EXPECT_TRUE(delegate.success());
 
-  EXPECT_EQ(0, System(string("mount -o loop ") + out_image + " " +
-                      TestDir() + "/mnt"));
   // Make sure everything in the out_image is there
-  expected_paths_vector.push_back("/update_engine_copy_success");
-  for (vector<string>::iterator it = expected_paths_vector.begin();
-       it != expected_paths_vector.end(); ++it) {
-    *it = TestDir() + "/mnt" + *it;
-  }
-  set<string> expected_paths(expected_paths_vector.begin(),
-                             expected_paths_vector.end());
-  VerifyAllPaths(TestDir() + "/mnt", expected_paths);
-  string file_data;
-  EXPECT_TRUE(utils::ReadFileToString(TestDir() + "/mnt/hi", &file_data));
-  EXPECT_EQ("hi\n", file_data);
-  EXPECT_TRUE(utils::ReadFileToString(TestDir() + "/mnt/hello", &file_data));
-  EXPECT_EQ("hello\n", file_data);
-  EXPECT_EQ("/some/target", Readlink(TestDir() + "/mnt/sym"));
-  EXPECT_EQ(0, System(string("umount -d ") + TestDir() + "/mnt"));
+  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));
+  EXPECT_TRUE(ExpectVectorsEq(a_loop_data, a_out));
 
-  EXPECT_EQ(0, unlink(out_image.c_str()));
-  EXPECT_EQ(0, rmdir((TestDir() + "/mnt").c_str()));
-
-  EXPECT_FALSE(copier_action.skipped_copy());
-  LOG(INFO) << "collected plan:";
-  collector_action.object().Dump();
-  LOG(INFO) << "expected plan:";
-  install_plan.Dump();
   EXPECT_TRUE(collector_action.object() == install_plan);
 }
 
@@ -262,12 +262,12 @@
   EXPECT_FALSE(delegate.success_);
 }
 
-TEST_F(FilesystemCopierActionTest, RunAsRootSkipUpdateTest) {
+TEST_F(FilesystemCopierActionTest, RunAsRootNoSpaceTest) {
   ASSERT_EQ(0, getuid());
   DoTest(true, false);
 }
 
-TEST_F(FilesystemCopierActionTest, RunAsRootNoSpaceTest) {
+TEST_F(FilesystemCopierActionTest, RunAsRootTerminateEarlyTest) {
   ASSERT_EQ(0, getuid());
   DoTest(false, true);
 }
diff --git a/testrunner.cc b/testrunner.cc
index 0a74480..933c25d 100644
--- a/testrunner.cc
+++ b/testrunner.cc
@@ -4,14 +4,21 @@
 
 // based on pam_google_testrunner.cc
 
+#include <dbus/dbus-glib.h>
+#include <dbus/dbus-glib-bindings.h>
+#include <dbus/dbus-glib-lowlevel.h>
 #include <glib.h>
+#include <glib-object.h>
 #include <gtest/gtest.h>
-
+#include "base/command_line.h"
 #include "update_engine/subprocess.h"
 
 int main(int argc, char **argv) {
+  ::g_type_init();
   g_thread_init(NULL);
+  dbus_g_thread_init();
   chromeos_update_engine::Subprocess::Init();
+  CommandLine::Init(argc, argv);
   ::testing::InitGoogleTest(&argc, argv);
   return RUN_ALL_TESTS();
 }
diff --git a/update_engine_client.cc b/update_engine_client.cc
index 1e273ed..bed7205 100644
--- a/update_engine_client.cc
+++ b/update_engine_client.cc
@@ -16,6 +16,7 @@
 using chromeos_update_engine::kUpdateEngineServiceName;
 using chromeos_update_engine::kUpdateEngineServicePath;
 using chromeos_update_engine::kUpdateEngineServiceInterface;
+using chromeos_update_engine::utils::GetGErrorMessage;
 
 DEFINE_bool(status, false, "Print the status to stdout.");
 DEFINE_bool(force_update, false,
@@ -25,12 +26,6 @@
 
 namespace {
 
-const char* GetErrorMessage(const GError* error) {
-  if (!error)
-    return "Unknown error.";
-  return error->message;
-}
-
 bool GetStatus() {
   DBusGConnection *bus;
   DBusGProxy *proxy;
@@ -46,7 +41,7 @@
                                           kUpdateEngineServiceInterface,
                                           &error);
   if (!proxy) {
-    LOG(FATAL) << "Error getting proxy: " << GetErrorMessage(error);
+    LOG(FATAL) << "Error getting proxy: " << GetGErrorMessage(error);
   }
 
   gint64 last_checked_time = 0;
@@ -64,7 +59,7 @@
       &new_size,
       &error);
   if (rc == FALSE) {
-    LOG(INFO) << "Error getting status: " << GetErrorMessage(error);
+    LOG(INFO) << "Error getting status: " << GetGErrorMessage(error);
   }
   printf("LAST_CHECKED_TIME=%" PRIi64 "\nPROGRESS=%f\nCURRENT_OP=%s\n"
          "NEW_VERSION=%s\nNEW_SIZE=%" PRIi64 "\n",
diff --git a/utils.cc b/utils.cc
index 7f05ffb..724a4ba 100644
--- a/utils.cc
+++ b/utils.cc
@@ -333,6 +333,12 @@
   return true;
 }
 
+const char* GetGErrorMessage(const GError* error) {
+  if (!error)
+    return "Unknown error.";
+  return error->message;
+}
+
 const char* const kStatefulPartition = "/mnt/stateful_partition";
 
 }  // namespace utils
diff --git a/utils.h b/utils.h
index bcbd8da..7b6fc10 100644
--- a/utils.h
+++ b/utils.h
@@ -10,6 +10,7 @@
 #include <set>
 #include <string>
 #include <vector>
+#include <glib.h>
 #include "update_engine/action.h"
 #include "update_engine/action_processor.h"
 
@@ -78,6 +79,9 @@
                      unsigned long flags);
 bool UnmountFilesystem(const std::string& mountpoint);
 
+// Returns the error message, if any, from a GError pointer.
+const char* GetGErrorMessage(const GError* error);
+
 // Log a string in hex to LOG(INFO). Useful for debugging.
 void HexDumpArray(const unsigned char* const arr, const size_t length);
 inline void HexDumpString(const std::string& str) {