AU: Beginnings of delta support

- proto file for delta files; still needs hardlink support

- code to generate a delta update from two directory trees (old, new).

- code to parse delta update

- Actions: postinst-runner, install, bootable flag setter, filesystem
 copier, Omaha response handler, Omaha request preparer,

- misc utility functions, like StringHasSuffix(), templatized Action
 classes to feed/collect an object from another action.

- FilesystemIterator: iterates a directory tree with optional
 exclusion path. Tolerates deleting of files during iteration.

- Subprocess class: support for synchronously or asynchronously
 running an external command. Doesn't pass any env variables.

- Integration test that strings many Actions together and tests using
 actual Omaha/Lorry. Currently only tests full updates.

- New simple HTTP server for unittest that supports fake flaky
 connections.

- Some refactoring.

Review URL: http://codereview.chromium.org/466036


git-svn-id: svn://chrome-svn/chromeos/trunk@334 06c00378-0e64-4dae-be16-12b19f9950a1
diff --git a/SConstruct b/SConstruct
index fa03602..64406e4 100644
--- a/SConstruct
+++ b/SConstruct
@@ -2,41 +2,137 @@
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
+# Protobuffer compilation
+""" Inputs:
+        target: list of targets to compile to
+        source: list of sources to compile
+        env: the scons environment in which we are compiling
+    Outputs:
+        target: the list of targets we'll emit
+        source: the list of sources we'll compile"""
+def ProtocolBufferEmitter(target, source, env):
+  output = str(source[0])
+  output = output[0:output.rfind('.proto')]
+  target = [
+    output + '.pb.cc',
+    output + '.pb.h',
+  ]
+  return target, source
+
+""" Inputs:
+        source: list of sources to process
+        target: list of targets to generate
+        env: scons environment in which we are working
+        for_signature: unused
+    Outputs: a list of commands to execute to generate the targets from
+             the sources."""
+def ProtocolBufferGenerator(source, target, env, for_signature):
+  commands = [
+    '/usr/bin/protoc '
+    ' --proto_path . ${SOURCES} --cpp_out .']
+  return commands
+
+proto_builder = Builder(generator = ProtocolBufferGenerator,
+                        emitter = ProtocolBufferEmitter,
+                        single_source = 1,
+                        suffix = '.pb.cc')
+
 env = Environment()
-env['CCFLAGS'] = '-g -fno-exceptions -Wall -Werror -D_FILE_OFFSET_BITS=64 ' + \
-                 '-I/usr/include/libxml2'
-env['LIBS'] = Split('curl gtest ssl xml2 z')
-env['CPPPATH'] = ['..']
+env['CCFLAGS'] = ' '.join("""-g
+                             -fno-exceptions
+                             -Wall
+                             -Werror
+                             -Wclobbered
+                             -Wempty-body
+                             -Wignored-qualifiers
+                             -Wmissing-field-initializers
+                             -Wsign-compare
+                             -Wtype-limits
+                             -Wuninitialized
+                             -D_FILE_OFFSET_BITS=64
+                             -I/usr/include/libxml2""".split());
+
+env['LIBS'] = Split("""base
+                       curl
+                       gflags
+                       glib-2.0
+                       gtest
+                       gthread-2.0
+                       libpcrecpp
+                       protobuf
+                       pthread
+                       ssl
+                       xml2
+                       z""")
+env['CPPPATH'] = ['..', '../../third_party/chrome/files', '../../common']
+env['LIBPATH'] = ['../../third_party/chrome']
+env['BUILDERS']['ProtocolBuffer'] = proto_builder
 env.ParseConfig('pkg-config --cflags --libs glib-2.0')
+env.ProtocolBuffer('update_metadata.pb.cc', 'update_metadata.proto')
 
 if ARGUMENTS.get('debug', 0):
   env['CCFLAGS'] += ' -fprofile-arcs -ftest-coverage'
   env['LIBS'] += ['gcov']
 
+
+
 sources = Split("""action_processor.cc
                    decompressing_file_writer.cc
+                   delta_diff_parser.cc
                    download_action.cc
+                   filesystem_copier_action.cc
+                   filesystem_iterator.cc
+                   file_writer.cc
+                   gzip.cc
+                   install_action.cc
                    libcurl_http_fetcher.cc
                    omaha_hash_calculator.cc
-                   update_check_action.cc""")
+                   omaha_request_prep_action.cc
+                   omaha_response_handler_action.cc
+                   postinstall_runner_action.cc
+                   set_bootable_flag_action.cc
+                   subprocess.cc
+                   update_check_action.cc
+		               update_metadata.pb.cc
+		               utils.cc""")
 main = ['main.cc']
 
 unittest_sources = Split("""action_unittest.cc
                             action_pipe_unittest.cc
                             action_processor_unittest.cc
                             decompressing_file_writer_unittest.cc
+                            delta_diff_generator_unittest.cc
                             download_action_unittest.cc
                             file_writer_unittest.cc
+                            filesystem_copier_action_unittest.cc
+                            filesystem_iterator_unittest.cc
+                            gzip_unittest.cc
                             http_fetcher_unittest.cc
+                            install_action_unittest.cc
+                            integration_unittest.cc
                             mock_http_fetcher.cc
                             omaha_hash_calculator_unittest.cc
+                            omaha_request_prep_action_unittest.cc
+                            omaha_response_handler_action_unittest.cc
+                            postinstall_runner_action_unittest.cc
+                            set_bootable_flag_action_unittest.cc
+                            subprocess_unittest.cc
                             test_utils.cc
-                            update_check_action_unittest.cc""")
+                            update_check_action_unittest.cc
+                            utils_unittest.cc""")
 unittest_main = ['testrunner.cc']
 
+delta_generator_sources = Split("""delta_diff_generator.cc""")
+
 env.Program('update_engine', sources + main)
 unittest_cmd = env.Program('update_engine_unittests',
-            sources + unittest_sources + unittest_main)
+                           sources + delta_generator_sources +
+                           unittest_sources + unittest_main)
 
 Clean(unittest_cmd, Glob('*.gcda') + Glob('*.gcno') + Glob('*.gcov') +
                     Split('html app.info'))
+
+delta_generator_cmd = env.Program('delta_generator',
+                                  sources + delta_generator_sources + main)
+
+http_server_cmd = env.Program('test_http_server', 'test_http_server.cc')
diff --git a/action.h b/action.h
index 15f72df..af6deee 100644
--- a/action.h
+++ b/action.h
@@ -2,17 +2,16 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef UPDATE_ENGINE_ACTION_H__
-#define UPDATE_ENGINE_ACTION_H__
+#ifndef CHROMEOS_PLATFORM_UPDATE_ENGINE_ACTION_H__
+#define CHROMEOS_PLATFORM_UPDATE_ENGINE_ACTION_H__
 
 #include <stdio.h>
 #include <tr1/memory>
 #include <iostream>
-
 #include "base/basictypes.h"
-#include <base/logging.h>
-
+#include "chromeos/obsolete_logging.h"
 #include "update_engine/action_processor.h"
+#include "update_engine/action_pipe.h"
 
 // The structure of these classes (Action, ActionPipe, ActionProcessor, etc.)
 // is based on the KSAction* classes from the Google Update Engine code at
@@ -157,7 +156,7 @@
           out_pipe) {
     out_pipe_ = out_pipe;
   }
- protected:
+
   // Returns true iff there is an associated input pipe. If there's an input
   // pipe, there's an input object, but it may have been constructed with the
   // default ctor if the previous action didn't call SetOutputObject().
@@ -184,6 +183,13 @@
     out_pipe_->set_contents(out_obj);
   }
 
+  // Returns a reference to the object sitting in the output pipe.
+  const typename ActionTraits<SubClass>::OutputObjectType& GetOutputObject() {
+    CHECK(HasOutputPipe());
+    return out_pipe_->contents();
+  }
+
+protected:
   // We use a shared_ptr to the pipe. shared_ptr objects destroy what they
   // point to when the last such shared_ptr object dies. We consider the
   // Actions on either end of a pipe to "own" the pipe. When the last Action
@@ -196,4 +202,4 @@
 
 };  // namespace chromeos_update_engine
 
-#endif  // UPDATE_ENGINE_ACTION_H__
+#endif  // CHROMEOS_PLATFORM_UPDATE_ENGINE_ACTION_H__
diff --git a/action_pipe.h b/action_pipe.h
index 8ece015..509fbee 100644
--- a/action_pipe.h
+++ b/action_pipe.h
@@ -2,16 +2,16 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef UPDATE_ENGINE_PIPE_H__
-#define UPDATE_ENGINE_PIPE_H__
+#ifndef CHROMEOS_PLATFORM_UPDATE_ENGINE_PIPE_H__
+#define CHROMEOS_PLATFORM_UPDATE_ENGINE_PIPE_H__
 
 #include <stdio.h>
 #include <iostream>
 #include <map>
 #include <string>
 #include <tr1/memory>
-#include <base/logging.h>
 #include "base/basictypes.h"
+#include "chromeos/obsolete_logging.h"
 
 // The structure of these classes (Action, ActionPipe, ActionProcessor, etc.)
 // is based on the KSAction* classes from the Google Update Engine code at
@@ -91,4 +91,4 @@
 
 }  // namespace chromeos_update_engine
 
-#endif  // UPDATE_ENGINE_PIPE_H__
+#endif  // CHROMEOS_PLATFORM_UPDATE_ENGINE_PIPE_H__
diff --git a/action_processor.cc b/action_processor.cc
index 623e3d2..21a88f1 100644
--- a/action_processor.cc
+++ b/action_processor.cc
@@ -3,6 +3,7 @@
 // found in the LICENSE file.
 
 #include "update_engine/action_processor.h"
+#include "chromeos/obsolete_logging.h"
 #include "update_engine/action.h"
 
 namespace chromeos_update_engine {
@@ -49,7 +50,7 @@
     delegate_->ProcessingStopped(this);
 }
 
-void ActionProcessor::ActionComplete(const AbstractAction* actionptr,
+void ActionProcessor::ActionComplete(AbstractAction* actionptr,
                                      bool success) {
   CHECK_EQ(actionptr, current_action_);
   if (delegate_)
@@ -60,6 +61,14 @@
   if (actions_.empty()) {
     LOG(INFO) << "ActionProcessor::ActionComplete: finished last action of"
                  " type " << old_type;
+  } else if (!success) {
+    LOG(INFO) << "ActionProcessor::ActionComplete: " << old_type
+              << " action failed. Aborting processing.";
+    actions_.clear();
+  }
+  if (actions_.empty()) {
+    LOG(INFO) << "ActionProcessor::ActionComplete: finished last action of"
+                 " type " << old_type;
     if (delegate_) {
       delegate_->ProcessingDone(this);
     }
diff --git a/action_processor.h b/action_processor.h
index d478477..938a81d 100644
--- a/action_processor.h
+++ b/action_processor.h
@@ -2,8 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef UPDATE_ENGINE_ACTION_PROCESSOR_H__
-#define UPDATE_ENGINE_ACTION_PROCESSOR_H__
+#ifndef CHROMEOS_PLATFORM_UPDATE_ENGINE_ACTION_PROCESSOR_H__
+#define CHROMEOS_PLATFORM_UPDATE_ENGINE_ACTION_PROCESSOR_H__
 
 #include <deque>
 
@@ -57,7 +57,7 @@
   }
 
   // Called by an action to notify processor that it's done. Caller passes self.
-  void ActionComplete(const AbstractAction* actionptr, bool success);
+  void ActionComplete(AbstractAction* actionptr, bool success);
 
  private:
   // Actions that have not yet begun processing, in the order in which
@@ -87,11 +87,11 @@
 
   // Called whenever an action has finished processing, either successfully
   // or otherwise.
-  virtual void ActionCompleted(const ActionProcessor* processor,
-                               const AbstractAction* action,
+  virtual void ActionCompleted(ActionProcessor* processor,
+                               AbstractAction* action,
                                bool success) {}
 };
 
 }  // namespace chromeos_update_engine
 
-#endif  // UPDATE_ENGINE_ACTION_PROCESSOR_H__
+#endif  // CHROMEOS_PLATFORM_UPDATE_ENGINE_ACTION_PROCESSOR_H__
diff --git a/action_processor_unittest.cc b/action_processor_unittest.cc
index 2a80bc3..6130585 100644
--- a/action_processor_unittest.cc
+++ b/action_processor_unittest.cc
@@ -70,8 +70,8 @@
     EXPECT_FALSE(processing_stopped_called_);
     processing_stopped_called_ = true;
   }
-  virtual void ActionCompleted(const ActionProcessor* processor,
-                               const AbstractAction* action,
+  virtual void ActionCompleted(ActionProcessor* processor,
+                               AbstractAction* action,
                                bool success) {
     EXPECT_EQ(processor_, processor);
     EXPECT_FALSE(action_completed_called_);
diff --git a/decompressing_file_writer.cc b/decompressing_file_writer.cc
index 15bd4fd..96db010 100644
--- a/decompressing_file_writer.cc
+++ b/decompressing_file_writer.cc
@@ -37,7 +37,10 @@
   // so not all data we get fed to us this time will necessarily
   // be written out this time (in decompressed form).
 
-  CHECK_EQ(0, stream_.avail_in);
+  if (stream_.avail_in) {
+    LOG(ERROR) << "Have data already. Bailing";
+    return -1;
+  }
   char buf[1024];
 
   buffer_.reserve(count);
diff --git a/decompressing_file_writer.h b/decompressing_file_writer.h
index 975ca3c..6f12e87 100644
--- a/decompressing_file_writer.h
+++ b/decompressing_file_writer.h
@@ -2,8 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef UPDATE_ENGINE_DECOMPRESSING_FILE_WRITER_H__
-#define UPDATE_ENGINE_DECOMPRESSING_FILE_WRITER_H__
+#ifndef CHROMEOS_PLATFORM_UPDATE_ENGINE_DECOMPRESSING_FILE_WRITER_H__
+#define CHROMEOS_PLATFORM_UPDATE_ENGINE_DECOMPRESSING_FILE_WRITER_H__
 
 #include <zlib.h>
 #include "base/basictypes.h"
@@ -57,4 +57,4 @@
 
 }  // namespace chromeos_update_engine
 
-#endif  // UPDATE_ENGINE_DECOMPRESSING_FILE_WRITER_H__
+#endif  // CHROMEOS_PLATFORM_UPDATE_ENGINE_DECOMPRESSING_FILE_WRITER_H__
diff --git a/download_action.cc b/download_action.cc
index 3a039d1..833f806 100644
--- a/download_action.cc
+++ b/download_action.cc
@@ -2,23 +2,19 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include <glib.h>
-
-#include "update_engine/action_pipe.h"
 #include "update_engine/download_action.h"
+#include <errno.h>
+#include <algorithm>
+#include <glib.h>
+#include "update_engine/action_pipe.h"
+
+using std::min;
 
 namespace chromeos_update_engine {
 
-DownloadAction::DownloadAction(const std::string& url,
-                               const std::string& output_path,
-                               off_t size, const std::string& hash,
-                               const bool should_decompress,
-                               HttpFetcher* http_fetcher)
-    : size_(size),
-      url_(url),
-      output_path_(output_path),
-      hash_(hash),
-      should_decompress_(should_decompress),
+DownloadAction::DownloadAction(HttpFetcher* http_fetcher)
+    : size_(0),
+      should_decompress_(false),
       writer_(NULL),
       http_fetcher_(http_fetcher) {}
 
@@ -29,14 +25,24 @@
   CHECK(!writer_);
   direct_file_writer_.reset(new DirectFileWriter);
 
+  // Get the InstallPlan and read it
+  CHECK(HasInputObject());
+  InstallPlan install_plan(GetInputObject());
+
+  should_decompress_ = install_plan.is_full_update;
+  url_ = install_plan.download_url;
+  output_path_ = install_plan.download_path;
+  hash_ = install_plan.download_hash;
+  install_plan.Dump();
+
   if (should_decompress_) {
     decompressing_file_writer_.reset(
         new GzipDecompressingFileWriter(direct_file_writer_.get()));
     writer_ = decompressing_file_writer_.get();
+    output_path_ = install_plan.install_path;
   } else {
     writer_ = direct_file_writer_.get();
   }
-
   int rc = writer_->Open(output_path_.c_str(),
                          O_TRUNC | O_WRONLY | O_CREAT | O_LARGEFILE, 0644);
   if (rc < 0) {
@@ -58,35 +64,29 @@
 void DownloadAction::ReceivedBytes(HttpFetcher *fetcher,
                                    const char* bytes,
                                    int length) {
-  int bytes_written = 0;
-  do {
-    CHECK_GT(length, bytes_written);
-    int rc = writer_->Write(bytes + bytes_written, length - bytes_written);
-    // TODO(adlr): handle write error
-    CHECK_GE(rc, 0);
-    bytes_written += rc;
-  } while (length != bytes_written);
+  int rc = writer_->Write(bytes, length);
+  TEST_AND_RETURN(rc >= 0);
   omaha_hash_calculator_.Update(bytes, length);
 }
 
 void DownloadAction::TransferComplete(HttpFetcher *fetcher, bool successful) {
   if (writer_) {
-    CHECK_EQ(0, writer_->Close()) << errno;
+    CHECK_EQ(writer_->Close(), 0) << errno;
     writer_ = NULL;
   }
   if (successful) {
     // Make sure hash is correct
     omaha_hash_calculator_.Finalize();
     if (omaha_hash_calculator_.hash() != hash_) {
-      LOG(INFO) << "Download of " << url_ << " failed. Expect hash "
-                << hash_ << " but got hash " << omaha_hash_calculator_.hash();
+      LOG(ERROR) << "Download of " << url_ << " failed. Expect hash "
+                 << hash_ << " but got hash " << omaha_hash_calculator_.hash();
       successful = false;
     }
   }
 
   // Write the path to the output pipe if we're successful
   if (successful && HasOutputPipe())
-    SetOutputObject(output_path_);
+    SetOutputObject(GetInputObject());
   processor_->ActionComplete(this, successful);
 }
 
diff --git a/download_action.h b/download_action.h
index 9abbdcf..e6939e1 100644
--- a/download_action.h
+++ b/download_action.h
@@ -2,8 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef UPDATE_ENGINE_DOWNLOAD_ACTION_H__
-#define UPDATE_ENGINE_DOWNLOAD_ACTION_H__
+#ifndef CHROMEOS_PLATFORM_UPDATE_ENGINE_DOWNLOAD_ACTION_H__
+#define CHROMEOS_PLATFORM_UPDATE_ENGINE_DOWNLOAD_ACTION_H__
 
 #include <sys/types.h>
 #include <sys/stat.h>
@@ -19,11 +19,11 @@
 #include "update_engine/decompressing_file_writer.h"
 #include "update_engine/file_writer.h"
 #include "update_engine/http_fetcher.h"
+#include "update_engine/install_plan.h"
 #include "update_engine/omaha_hash_calculator.h"
 
 // The Download Action downloads a requested url to a specified path on disk.
-// It takes no input object, but if successful, passes the output path
-// to the output pipe.
+// The url and output path are determined by the InstallPlan passed in.
 
 using std::map;
 using std::string;
@@ -36,10 +36,9 @@
 template<>
 class ActionTraits<DownloadAction> {
  public:
-  // Does not take an object for input
-  typedef NoneType InputObjectType;
-  // On success, puts the output path on output
-  typedef std::string OutputObjectType;
+  // Takes and returns an InstallPlan
+  typedef InstallPlan InputObjectType;
+  typedef InstallPlan OutputObjectType;
 };
 
 class DownloadAction : public Action<DownloadAction>,
@@ -47,11 +46,8 @@
  public:
   // Takes ownership of the passed in HttpFetcher. Useful for testing.
   // A good calling pattern is:
-  // DownloadAction(..., new WhateverHttpFetcher);
-  DownloadAction(const std::string& url, const std::string& output_path,
-                 off_t size, const std::string& hash,
-                 const bool should_decompress,
-                 HttpFetcher* http_fetcher);
+  // DownloadAction(new WhateverHttpFetcher);
+  DownloadAction(HttpFetcher* http_fetcher);
   virtual ~DownloadAction();
   typedef ActionTraits<DownloadAction>::InputObjectType InputObjectType;
   typedef ActionTraits<DownloadAction>::OutputObjectType OutputObjectType;
@@ -59,7 +55,8 @@
   void TerminateProcessing();
 
   // Debugging/logging
-  std::string Type() const { return "DownloadAction"; }
+  static std::string StaticType() { return "DownloadAction"; }
+  std::string Type() const { return StaticType(); }
 
   // Delegate methods (see http_fetcher.h)
   virtual void ReceivedBytes(HttpFetcher *fetcher,
@@ -71,17 +68,17 @@
   const size_t size_;
 
   // URL to download
-  const std::string url_;
+  std::string url_;
 
   // Path to save URL to
-  const std::string output_path_;
+  std::string output_path_;
 
   // Expected hash of the file. The hash must match for this action to
   // succeed.
-  const std::string hash_;
+  std::string hash_;
 
   // Whether the caller requested that we decompress the downloaded data.
-  const bool should_decompress_;
+  bool should_decompress_;
 
   // The FileWriter that downloaded data should be written to. It will
   // either point to *decompressing_file_writer_ or *direct_file_writer_.
@@ -107,4 +104,4 @@
 
 }  // namespace chromeos_update_engine
 
-#endif  // UPDATE_ENGINE_DOWNLOAD_ACTION_H__
+#endif  // CHROMEOS_PLATFORM_UPDATE_ENGINE_DOWNLOAD_ACTION_H__
diff --git a/download_action_unittest.cc b/download_action_unittest.cc
index 3dfc005..4276dc9 100644
--- a/download_action_unittest.cc
+++ b/download_action_unittest.cc
@@ -11,6 +11,7 @@
 #include "update_engine/mock_http_fetcher.h"
 #include "update_engine/omaha_hash_calculator.h"
 #include "update_engine/test_utils.h"
+#include "update_engine/utils.h"
 
 namespace chromeos_update_engine {
 
@@ -30,7 +31,8 @@
   virtual void ProcessingDone(const ActionProcessor* processor) {
     ASSERT_TRUE(loop_);
     g_main_loop_quit(loop_);
-    vector<char> found_data = ReadFile(path_);
+    vector<char> found_data;
+    ASSERT_TRUE(utils::ReadFile(path_, &found_data));
     ASSERT_EQ(expected_data_.size(), found_data.size());
     for (unsigned i = 0; i < expected_data_.size(); i++) {
       EXPECT_EQ(expected_data_[i], found_data[i]);
@@ -38,8 +40,8 @@
     processing_done_called_ = true;
   }
 
-  virtual void ActionCompleted(const ActionProcessor* processor,
-                               const AbstractAction* action,
+  virtual void ActionCompleted(ActionProcessor* processor,
+                               AbstractAction* action,
                                bool success) {
     // make sure actions always succeed
     EXPECT_TRUE(success);
@@ -76,16 +78,22 @@
   // TODO(adlr): see if we need a different file for build bots
   const string path("/tmp/DownloadActionTest");
   // takes ownership of passed in HttpFetcher
-  DownloadAction download_action("", path, 0,
-                                 OmahaHashCalculator::OmahaHashOfData(use_data),
-                                 compress, new MockHttpFetcher(&use_data[0],
-                                 use_data.size()));
+  InstallPlan install_plan(compress, "",
+                           OmahaHashCalculator::OmahaHashOfData(use_data),
+                           compress ? "" : path, compress ? path : "");
+  ObjectFeederAction<InstallPlan> feeder_action;
+  feeder_action.set_obj(install_plan);
+  DownloadAction download_action(new MockHttpFetcher(&use_data[0],
+                                                     use_data.size()));
+  BondActions(&feeder_action, &download_action);
+
   DownloadActionTestProcessorDelegate delegate;
   delegate.loop_ = loop;
   delegate.expected_data_ = data;
   delegate.path_ = path;
   ActionProcessor processor;
   processor.set_delegate(&delegate);
+  processor.EnqueueAction(&feeder_action);
   processor.EnqueueAction(&download_action);
 
   g_timeout_add(0, &StartProcessorInRunLoop, &processor);
@@ -148,13 +156,17 @@
   const string path("/tmp/DownloadActionTest");
   {
     // takes ownership of passed in HttpFetcher
-    DownloadAction download_action("", path, 0, "", false,
-        new MockHttpFetcher(&data[0], data.size()));
+    ObjectFeederAction<InstallPlan> feeder_action;
+    InstallPlan install_plan(false, "", "", path, "");
+    feeder_action.set_obj(install_plan);
+    DownloadAction download_action(new MockHttpFetcher(&data[0], data.size()));
     TerminateEarlyTestProcessorDelegate delegate;
     delegate.loop_ = loop;
     ActionProcessor processor;
     processor.set_delegate(&delegate);
+    processor.EnqueueAction(&feeder_action);
     processor.EnqueueAction(&download_action);
+    BondActions(&feeder_action, &download_action);
 
     g_timeout_add(0, &TerminateEarlyTestStarter, &processor);
     g_main_loop_run(loop);
@@ -172,27 +184,27 @@
 template<>
 class ActionTraits<DownloadActionTestAction> {
  public:
-  typedef string OutputObjectType;
-  typedef string InputObjectType;
+  typedef InstallPlan OutputObjectType;
+  typedef InstallPlan InputObjectType;
 };
 
 // This is a simple Action class for testing.
 struct DownloadActionTestAction : public Action<DownloadActionTestAction> {
   DownloadActionTestAction() : did_run_(false) {}
-  typedef string InputObjectType;
-  typedef string OutputObjectType;
-  ActionPipe<string>* in_pipe() { return in_pipe_.get(); }
-  ActionPipe<string>* out_pipe() { return out_pipe_.get(); }
+  typedef InstallPlan InputObjectType;
+  typedef InstallPlan OutputObjectType;
+  ActionPipe<InstallPlan>* in_pipe() { return in_pipe_.get(); }
+  ActionPipe<InstallPlan>* out_pipe() { return out_pipe_.get(); }
   ActionProcessor* processor() { return processor_; }
   void PerformAction() {
     did_run_ = true;
     ASSERT_TRUE(HasInputObject());
-    EXPECT_EQ(expected_input_object_, GetInputObject());
+    EXPECT_TRUE(expected_input_object_ == GetInputObject());
     ASSERT_TRUE(processor());
     processor()->ActionComplete(this, true);
   }
   string Type() const { return "DownloadActionTestAction"; }
-  string expected_input_object_;
+  InstallPlan expected_input_object_;
   bool did_run_;
 };
 
@@ -222,18 +234,23 @@
   const string path("/tmp/DownloadActionTest");
 
   // takes ownership of passed in HttpFetcher
-  DownloadAction download_action("", path, 0,
-                                 OmahaHashCalculator::OmahaHashOfString("x"),
-                                 false, new MockHttpFetcher("x", 1));
+  InstallPlan install_plan(false, "",
+                           OmahaHashCalculator::OmahaHashOfString("x"), path,
+                           "");
+  ObjectFeederAction<InstallPlan> feeder_action;
+  feeder_action.set_obj(install_plan);
+  DownloadAction download_action(new MockHttpFetcher("x", 1));
 
   DownloadActionTestAction test_action;
-  test_action.expected_input_object_ = path;
+  test_action.expected_input_object_ = install_plan;
+  BondActions(&feeder_action, &download_action);
   BondActions(&download_action, &test_action);
 
   ActionProcessor processor;
   PassObjectOutTestProcessorDelegate delegate;
   delegate.loop_ = loop;
   processor.set_delegate(&delegate);
+  processor.EnqueueAction(&feeder_action);
   processor.EnqueueAction(&download_action);
   processor.EnqueueAction(&test_action);
 
@@ -250,10 +267,14 @@
   const string path("/fake/path/that/cant/be/created/because/of/missing/dirs");
 
   // takes ownership of passed in HttpFetcher
-  DownloadAction download_action("", path, 0, "", false,
-                                 new MockHttpFetcher("x", 1));
+  InstallPlan install_plan(false, "", "", path, "");
+  ObjectFeederAction<InstallPlan> feeder_action;
+  feeder_action.set_obj(install_plan);
+  DownloadAction download_action(new MockHttpFetcher("x", 1));
+  BondActions(&feeder_action, &download_action);
 
   ActionProcessor processor;
+  processor.EnqueueAction(&feeder_action);
   processor.EnqueueAction(&download_action);
   processor.StartProcessing();
   ASSERT_FALSE(processor.IsRunning());
diff --git a/file_writer.h b/file_writer.h
index 3b44a26..c506c01 100644
--- a/file_writer.h
+++ b/file_writer.h
@@ -2,14 +2,15 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef UPDATE_ENGINE_FILE_WRITER_H__
-#define UPDATE_ENGINE_FILE_WRITER_H__
+#ifndef CHROMEOS_PLATFORM_UPDATE_ENGINE_FILE_WRITER_H__
+#define CHROMEOS_PLATFORM_UPDATE_ENGINE_FILE_WRITER_H__
 
 #include <sys/types.h>
 #include <sys/stat.h>
 #include <fcntl.h>
 #include <unistd.h>
-#include "base/logging.h"
+#include "chromeos/obsolete_logging.h"
+#include "update_engine/utils.h"
 
 // FileWriter is a class that is used to (synchronously, for now) write to
 // a file. This file is a thin wrapper around open/write/close system calls,
@@ -41,39 +42,29 @@
   DirectFileWriter() : fd_(-1) {}
   virtual ~DirectFileWriter() {}
 
-  virtual int Open(const char* path, int flags, mode_t mode) {
-    CHECK_EQ(-1, fd_);
-    fd_ = open(path, flags, mode);
-    if (fd_ < 0)
-      return -errno;
-    return 0;
-  }
+  virtual int Open(const char* path, int flags, mode_t mode);
+  virtual int Write(const void* bytes, size_t count);
+  virtual int Close();
 
-  virtual int Write(const void* bytes, size_t count) {
-    CHECK_GE(fd_, 0);
-    ssize_t rc = write(fd_, bytes, count);
-    if (rc < 0)
-      return -errno;
-    return rc;
-  }
-
-  virtual int Close() {
-    CHECK_GE(fd_, 0);
-    int rc = close(fd_);
-
-    // This can be any negative number that's not -1. This way, this FileWriter
-    // won't be used again for another file.
-    fd_ = -2;
-
-    if (rc < 0)
-      return -errno;
-    return rc;
-  }
+  int fd() const { return fd_; }
 
  private:
   int fd_;
 };
 
+class ScopedFileWriterCloser {
+ public:
+  explicit ScopedFileWriterCloser(FileWriter* writer) : writer_(writer) {}
+  ~ScopedFileWriterCloser() {
+    int err = writer_->Close();
+    if (err)
+      LOG(ERROR) << "FileWriter::Close failed: "
+                 << utils::ErrnoNumberAsString(-err);
+  }
+ private:
+  FileWriter* writer_;
+};
+
 }  // namespace chromeos_update_engine
 
-#endif  // UPDATE_ENGINE_FILE_WRITER_H__
+#endif  // CHROMEOS_PLATFORM_UPDATE_ENGINE_FILE_WRITER_H__
diff --git a/file_writer_unittest.cc b/file_writer_unittest.cc
index ed8bcaa..6e494ec 100644
--- a/file_writer_unittest.cc
+++ b/file_writer_unittest.cc
@@ -2,6 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+#include <errno.h>
 #include <string.h>
 #include <unistd.h>
 #include <string>
@@ -9,6 +10,7 @@
 #include <gtest/gtest.h>
 #include "update_engine/file_writer.h"
 #include "update_engine/test_utils.h"
+#include "update_engine/utils.h"
 
 using std::string;
 using std::vector;
@@ -24,7 +26,8 @@
                                 O_CREAT | O_LARGEFILE | O_TRUNC | O_WRONLY,
                                 0644));
   ASSERT_EQ(4, file_writer.Write("test", 4));
-  vector<char> actual_data = ReadFile(path);
+  vector<char> actual_data;
+  EXPECT_TRUE(utils::ReadFile(path, &actual_data));
 
   EXPECT_FALSE(memcmp("test", &actual_data[0], actual_data.size()));
   EXPECT_EQ(0, file_writer.Close());
diff --git a/gen_coverage_html.sh b/gen_coverage_html.sh
index 7ccc5d8..1d3d586 100755
--- a/gen_coverage_html.sh
+++ b/gen_coverage_html.sh
@@ -6,10 +6,11 @@
 
 set -ex
 
-scons debug=1 -c
-scons debug=1
+scons debug=1 -j 2
 lcov -d . --zerocounters
-./update_engine_unittests
+./update_engine_unittests --gtest_filter='-*.RunAsRoot*:*.Fakeroot*'
+fakeroot ./update_engine_unittests --gtest_filter='*.Fakeroot*'
+sudo ./update_engine_unittests --gtest_filter='*.RunAsRoot*'
 lcov --directory . --capture --output-file app.info
 
 # some versions of genhtml support the --no-function-coverage argument,
@@ -19,3 +20,4 @@
 # is tested, but it shows only 50% function coverage b/c it thinks we didn't
 # test the prod version.
 genhtml --no-function-coverage -o html ./app.info || genhtml -o html ./app.info
+./local_coverage_rate.sh
diff --git a/http_fetcher.h b/http_fetcher.h
index 6e26906..5cd6f08 100644
--- a/http_fetcher.h
+++ b/http_fetcher.h
@@ -2,10 +2,11 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef UPDATE_ENGINE_HTTP_FETCHER_H__
-#define UPDATE_ENGINE_HTTP_FETCHER_H__
+#ifndef CHROMEOS_PLATFORM_UPDATE_ENGINE_HTTP_FETCHER_H__
+#define CHROMEOS_PLATFORM_UPDATE_ENGINE_HTTP_FETCHER_H__
 
 #include <string>
+#include <vector>
 #include <glib.h>
 #include "base/basictypes.h"
 
@@ -22,7 +23,7 @@
 
 class HttpFetcher {
  public:
-  HttpFetcher() : delegate_(NULL) {}
+  HttpFetcher() : post_data_set_(false), delegate_(NULL) {}
   virtual ~HttpFetcher() {}
   void set_delegate(HttpFetcherDelegate* delegate) {
     delegate_ = delegate;
@@ -87,4 +88,4 @@
 
 }  // namespace chromeos_update_engine
 
-#endif  // UPDATE_ENGINE_HTTP_FETCHER_H__
+#endif  // CHROMEOS_PLATFORM_UPDATE_ENGINE_HTTP_FETCHER_H__
diff --git a/http_fetcher_unittest.cc b/http_fetcher_unittest.cc
index f1e9c26..8d9c3f8 100644
--- a/http_fetcher_unittest.cc
+++ b/http_fetcher_unittest.cc
@@ -3,13 +3,18 @@
 // found in the LICENSE file.
 
 #include <unistd.h>
+#include <string>
+#include <vector>
 #include <base/scoped_ptr.h>
 #include <glib.h>
-#include <base/logging.h>
 #include <gtest/gtest.h>
+#include "chromeos/obsolete_logging.h"
 #include "update_engine/libcurl_http_fetcher.h"
 #include "update_engine/mock_http_fetcher.h"
 
+using std::string;
+using std::vector;
+
 namespace chromeos_update_engine {
 
 namespace {
@@ -27,6 +32,7 @@
   HttpFetcher* NewSmallFetcher() = 0;
   string BigUrl() const = 0;
   string SmallUrl() const = 0;
+  bool IsMock() const = 0;
 };
 
 class NullHttpServer {
@@ -53,13 +59,14 @@
   string SmallUrl() const {
     return "unused://unused";
   }
+  bool IsMock() const { return true; }
   typedef NullHttpServer HttpServer;
 };
 
 class PythonHttpServer {
  public:
   PythonHttpServer() {
-    char *argv[2] = {strdup("./test_http_server.py"), NULL};
+    char *argv[2] = {strdup("./test_http_server"), NULL};
     GError *err;
     started_ = false;
     if (!g_spawn_async(NULL,
@@ -74,7 +81,6 @@
     }
     int rc = 1;
     while (0 != rc) {
-      
       rc = system((string("wget --output-document=/dev/null ") +
                    LocalServerUrlForPath("/test")).c_str());
       usleep(10 * 1000);  // 10 ms
@@ -112,6 +118,7 @@
   string SmallUrl() const {
     return LocalServerUrlForPath("/foo");
   }
+  bool IsMock() const { return false; }
   typedef PythonHttpServer HttpServer;
 };
 
@@ -121,7 +128,7 @@
 
 namespace {
 class HttpFetcherTestDelegate : public HttpFetcherDelegate {
-public:
+ public:
   virtual void ReceivedBytes(HttpFetcher* fetcher,
                              const char* bytes, int length) {
     char str[length + 1];
@@ -133,6 +140,17 @@
   }
   GMainLoop* loop_;
 };
+
+struct StartTransferArgs {
+  HttpFetcher *http_fetcher;
+  string url;
+};
+
+gboolean StartTransfer(gpointer data) {
+  StartTransferArgs *args = reinterpret_cast<StartTransferArgs*>(data);
+  args->http_fetcher->BeginTransfer(args->url);
+  return FALSE;
+}
 }  // namespace {}
 
 TYPED_TEST(HttpFetcherTest, SimpleTest) {
@@ -146,7 +164,9 @@
     typename TestFixture::HttpServer server;
     ASSERT_TRUE(server.started_);
 
-    fetcher->BeginTransfer(this->SmallUrl());
+    StartTransferArgs start_xfer_args = {fetcher.get(), this->SmallUrl()};
+
+    g_timeout_add(0, StartTransfer, &start_xfer_args);
     g_main_loop_run(loop);
   }
   g_main_loop_unref(loop);
@@ -158,13 +178,11 @@
   virtual void ReceivedBytes(HttpFetcher* fetcher,
                              const char* bytes, int length) {
     char str[length + 1];
-    LOG(INFO) << "got " << length << " bytes";
     memset(str, 0, length + 1);
     memcpy(str, bytes, length);
     CHECK(!paused_);
     paused_ = true;
     fetcher->Pause();
-    LOG(INFO) << "calling pause";
   }
   virtual void TransferComplete(HttpFetcher* fetcher, bool successful) {
     g_main_loop_quit(loop_);
@@ -173,7 +191,6 @@
     CHECK(paused_);
     paused_ = false;
     fetcher_->Unpause();
-    LOG(INFO) << "calling unpause";
   }
   bool paused_;
   HttpFetcher* fetcher_;
@@ -274,4 +291,50 @@
   g_main_loop_unref(loop);
 }
 
+namespace {
+class FlakyHttpFetcherTestDelegate : public HttpFetcherDelegate {
+ public:
+  virtual void ReceivedBytes(HttpFetcher* fetcher,
+                             const char* bytes, int length) {
+    data.append(bytes, length);
+  }
+  virtual void TransferComplete(HttpFetcher* fetcher, bool successful) {
+    g_main_loop_quit(loop_);
+  }
+  string data;
+  GMainLoop* loop_;
+};
+}  // namespace {}
+
+TYPED_TEST(HttpFetcherTest, FlakyTest) {
+  if (this->IsMock())
+    return;
+  GMainLoop *loop = g_main_loop_new(g_main_context_default(), FALSE);
+  {
+    FlakyHttpFetcherTestDelegate delegate;
+    delegate.loop_ = loop;
+    scoped_ptr<HttpFetcher> fetcher(this->NewSmallFetcher());
+    fetcher->set_delegate(&delegate);
+
+    typename TestFixture::HttpServer server;
+    ASSERT_TRUE(server.started_);
+
+    StartTransferArgs start_xfer_args = {
+      fetcher.get(),
+      LocalServerUrlForPath("/flaky")
+    };
+
+    g_timeout_add(0, StartTransfer, &start_xfer_args);
+    g_main_loop_run(loop);
+
+    // verify the data we get back
+    ASSERT_EQ(100000, delegate.data.size());
+    for (int i = 0; i < 100000; i += 10) {
+      // Assert so that we don't flood the screen w/ EXPECT errors on failure.
+      ASSERT_EQ(delegate.data.substr(i, 10), "abcdefghij");
+    }
+  }
+  g_main_loop_unref(loop);
+}
+
 }  // namespace chromeos_update_engine
diff --git a/libcurl_http_fetcher.cc b/libcurl_http_fetcher.cc
index 624e6a9..9ed0c64 100644
--- a/libcurl_http_fetcher.cc
+++ b/libcurl_http_fetcher.cc
@@ -2,8 +2,12 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "base/logging.h"
 #include "update_engine/libcurl_http_fetcher.h"
+#include <algorithm>
+#include "chromeos/obsolete_logging.h"
+
+using std::max;
+using std::make_pair;
 
 // This is a concrete implementation of HttpFetcher that uses libcurl to do the
 // http work.
@@ -14,8 +18,7 @@
   CleanUp();
 }
 
-// Begins the transfer, which must not have already been started.
-void LibcurlHttpFetcher::BeginTransfer(const std::string& url) {
+void LibcurlHttpFetcher::ResumeTransfer(const std::string& url) {
   CHECK(!transfer_in_progress_);
   url_ = url;
   curl_multi_handle_ = curl_multi_init();
@@ -25,22 +28,40 @@
   CHECK(curl_handle_);
 
   if (post_data_set_) {
-    CHECK_EQ(CURLE_OK, curl_easy_setopt(curl_handle_, CURLOPT_POST, 1));
-    CHECK_EQ(CURLE_OK, curl_easy_setopt(curl_handle_, CURLOPT_POSTFIELDS,
-                                        &post_data_[0]));
-    CHECK_EQ(CURLE_OK, curl_easy_setopt(curl_handle_, CURLOPT_POSTFIELDSIZE,
-                                        post_data_.size()));
+    CHECK_EQ(curl_easy_setopt(curl_handle_, CURLOPT_POST, 1), CURLE_OK);
+    CHECK_EQ(curl_easy_setopt(curl_handle_, CURLOPT_POSTFIELDS,
+                              &post_data_[0]),
+             CURLE_OK);
+    CHECK_EQ(curl_easy_setopt(curl_handle_, CURLOPT_POSTFIELDSIZE,
+                              post_data_.size()),
+             CURLE_OK);
   }
 
-  CHECK_EQ(CURLE_OK, curl_easy_setopt(curl_handle_, CURLOPT_WRITEDATA, this));
-  CHECK_EQ(CURLE_OK, curl_easy_setopt(curl_handle_, CURLOPT_WRITEFUNCTION,
-                                      StaticLibcurlWrite));
-  CHECK_EQ(CURLE_OK, curl_easy_setopt(curl_handle_, CURLOPT_URL, url_.c_str()));
-  CHECK_EQ(CURLM_OK, curl_multi_add_handle(curl_multi_handle_, curl_handle_));
+  if (bytes_downloaded_ > 0) {
+    // Resume from where we left off
+    resume_offset_ = bytes_downloaded_;
+    CHECK_EQ(curl_easy_setopt(curl_handle_,
+                              CURLOPT_RESUME_FROM_LARGE,
+                              bytes_downloaded_), CURLE_OK);
+  }
+
+  CHECK_EQ(curl_easy_setopt(curl_handle_, CURLOPT_WRITEDATA, this), CURLE_OK);
+  CHECK_EQ(curl_easy_setopt(curl_handle_, CURLOPT_WRITEFUNCTION,
+                            StaticLibcurlWrite), CURLE_OK);
+  CHECK_EQ(curl_easy_setopt(curl_handle_, CURLOPT_URL, url_.c_str()), CURLE_OK);
+  CHECK_EQ(curl_multi_add_handle(curl_multi_handle_, curl_handle_), CURLM_OK);
   transfer_in_progress_ = true;
   CurlPerformOnce();
 }
 
+// Begins the transfer, which must not have already been started.
+void LibcurlHttpFetcher::BeginTransfer(const std::string& url) {
+  transfer_size_ = -1;
+  bytes_downloaded_ = 0;
+  resume_offset_ = 0;
+  ResumeTransfer(url);
+}
+
 void LibcurlHttpFetcher::TerminateTransfer() {
   CleanUp();
 }
@@ -59,8 +80,14 @@
   if (0 == running_handles) {
     // we're done!
     CleanUp();
-    if (delegate_)
-      delegate_->TransferComplete(this, true);  // success
+
+    if ((transfer_size_ >= 0) && (bytes_downloaded_ < transfer_size_)) {
+      ResumeTransfer(url_);
+    } else {
+      if (delegate_) {
+        delegate_->TransferComplete(this, true);  // success
+      }
+    }
   } else {
     // set up callback
     SetupMainloopSources();
@@ -68,6 +95,17 @@
 }
 
 size_t LibcurlHttpFetcher::LibcurlWrite(void *ptr, size_t size, size_t nmemb) {
+  {
+    double transfer_size_double;
+    CHECK_EQ(curl_easy_getinfo(curl_handle_,
+                               CURLINFO_CONTENT_LENGTH_DOWNLOAD,
+                               &transfer_size_double), CURLE_OK);
+    off_t new_transfer_size = static_cast<off_t>(transfer_size_double);
+    if (new_transfer_size > 0) {
+      transfer_size_ = resume_offset_ + new_transfer_size;
+    }
+  }
+  bytes_downloaded_ += size * nmemb;
   if (delegate_)
     delegate_->ReceivedBytes(this, reinterpret_cast<char*>(ptr), size * nmemb);
   return size * nmemb;
@@ -76,13 +114,13 @@
 void LibcurlHttpFetcher::Pause() {
   CHECK(curl_handle_);
   CHECK(transfer_in_progress_);
-  CHECK_EQ(CURLE_OK, curl_easy_pause(curl_handle_, CURLPAUSE_ALL));
+  CHECK_EQ(curl_easy_pause(curl_handle_, CURLPAUSE_ALL), CURLE_OK);
 }
 
 void LibcurlHttpFetcher::Unpause() {
   CHECK(curl_handle_);
   CHECK(transfer_in_progress_);
-  CHECK_EQ(CURLE_OK, curl_easy_pause(curl_handle_, CURLPAUSE_CONT));
+  CHECK_EQ(curl_easy_pause(curl_handle_, CURLPAUSE_CONT), CURLE_OK);
 }
 
 // This method sets up callbacks with the glib main loop.
@@ -99,8 +137,8 @@
 
   // Ask libcurl for the set of file descriptors we should track on its
   // behalf.
-  CHECK_EQ(CURLM_OK, curl_multi_fdset(curl_multi_handle_, &fd_read, &fd_write,
-                                      &fd_exec, &fd_max));
+  CHECK_EQ(curl_multi_fdset(curl_multi_handle_, &fd_read, &fd_write,
+                            &fd_exec, &fd_max), CURLM_OK);
 
   // We should iterate through all file descriptors up to libcurl's fd_max or
   // the highest one we're tracking, whichever is larger
@@ -113,7 +151,7 @@
   // in io_channels_ as there are fds that we're tracking.
   for (int i = 0; i <= fd_max; i++) {
     if (!(FD_ISSET(i, &fd_read) || FD_ISSET(i, &fd_write) ||
-        FD_ISSET(i, &fd_exec))) {
+          FD_ISSET(i, &fd_exec))) {
       // if we have an outstanding io_channel, remove it
       if (io_channels_.find(i) != io_channels_.end()) {
         g_source_remove(io_channels_[i].second);
@@ -139,7 +177,7 @@
 
   // Wet up a timeout callback for libcurl
   long ms = 0;
-  CHECK_EQ(CURLM_OK, curl_multi_timeout(curl_multi_handle_, &ms));
+  CHECK_EQ(curl_multi_timeout(curl_multi_handle_, &ms), CURLM_OK);
   if (ms < 0) {
     // From http://curl.haxx.se/libcurl/c/curl_multi_timeout.html:
     //     if libcurl returns a -1 timeout here, it just means that libcurl
@@ -210,14 +248,14 @@
 
   if (curl_handle_) {
     if (curl_multi_handle_) {
-      CHECK_EQ(CURLM_OK,
-               curl_multi_remove_handle(curl_multi_handle_, curl_handle_));
+      CHECK_EQ(curl_multi_remove_handle(curl_multi_handle_, curl_handle_),
+               CURLM_OK);
     }
     curl_easy_cleanup(curl_handle_);
     curl_handle_ = NULL;
   }
   if (curl_multi_handle_) {
-    CHECK_EQ(CURLM_OK, curl_multi_cleanup(curl_multi_handle_));
+    CHECK_EQ(curl_multi_cleanup(curl_multi_handle_), CURLM_OK);
     curl_multi_handle_ = NULL;
   }
   transfer_in_progress_ = false;
diff --git a/libcurl_http_fetcher.h b/libcurl_http_fetcher.h
index 433feeb..6f32183 100644
--- a/libcurl_http_fetcher.h
+++ b/libcurl_http_fetcher.h
@@ -2,15 +2,15 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef UPDATE_ENGINE_LIBCURL_HTTP_FETCHER_H__
-#define UPDATE_ENGINE_LIBCURL_HTTP_FETCHER_H__
+#ifndef CHROMEOS_PLATFORM_UPDATE_ENGINE_LIBCURL_HTTP_FETCHER_H__
+#define CHROMEOS_PLATFORM_UPDATE_ENGINE_LIBCURL_HTTP_FETCHER_H__
 
 #include <map>
 #include <string>
 #include <curl/curl.h>
 #include <glib.h>
 #include "base/basictypes.h"
-#include "base/logging.h"
+#include "chromeos/obsolete_logging.h"
 #include "update_engine/http_fetcher.h"
 
 // This is a concrete implementation of HttpFetcher that uses libcurl to do the
@@ -54,6 +54,10 @@
     idle_ms_ = ms;
   }
  private:
+  // Resumes a transfer where it left off. This will use the
+  // HTTP Range: header to make a new connection from where the last
+  // left off.
+  virtual void ResumeTransfer(const std::string& url);
 
   // These two methods are for glib main loop callbacks. They are called
   // when either a file descriptor is ready for work or when a timer
@@ -109,10 +113,20 @@
 
   bool transfer_in_progress_;
 
+  // The transfer size. -1 if not known.
+  off_t transfer_size_;
+
+  // How many bytes have been downloaded and sent to the delegate.
+  off_t bytes_downloaded_;
+
+  // If we resumed an earlier transfer, data offset that we used for the
+  // new connection.  0 otherwise.
+  off_t resume_offset_;
+
   long idle_ms_;
   DISALLOW_COPY_AND_ASSIGN(LibcurlHttpFetcher);
 };
 
 }  // namespace chromeos_update_engine
 
-#endif  // UPDATE_ENGINE_LIBCURL_HTTP_FETCHER_H__
+#endif  // CHROMEOS_PLATFORM_UPDATE_ENGINE_LIBCURL_HTTP_FETCHER_H__
diff --git a/main.cc b/main.cc
index 412020b..bbb6401 100644
--- a/main.cc
+++ b/main.cc
@@ -2,34 +2,12 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include <stdlib.h>
-#include <stdio.h>
-#include <iostream>
 #include <glib.h>
-#include <base/logging.h>
 
-#include <libxml/parser.h>
-#include <libxml/xpath.h>
-#include <libxml/xpathInternals.h>
-
-// This code runs inside the main loop
-gboolean EntryPoint(gpointer data) {
-  GMainLoop *loop = reinterpret_cast<GMainLoop*>(data);
-  LOG(INFO) << "Chrome OS Update Engine beginning";
-  g_main_loop_quit(loop);
-  return FALSE;
-}
+#include "update_engine/subprocess.h"
 
 int main(int argc, char** argv) {
-  xmlDocPtr doc = xmlNewDoc((const xmlChar*)"1.0");
-  CHECK(doc);
-  CHECK_EQ(argc, 2);
-  printf("enc: [%s]\n", xmlEncodeEntitiesReentrant(doc, (const xmlChar*)argv[1]));
-  return 0;
-  
-  
-  GMainLoop *loop = g_main_loop_new(g_main_context_default(), FALSE);
-  g_timeout_add(0, &EntryPoint, loop);
-  g_main_loop_run(loop);
+  g_thread_init(NULL);
+  chromeos_update_engine::Subprocess::Init();
   return 0;
 }
diff --git a/mock_file_writer.h b/mock_file_writer.h
index e6d1612..7cf0fd5 100644
--- a/mock_file_writer.h
+++ b/mock_file_writer.h
@@ -2,8 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef UPDATE_ENGINE_MOCK_FILE_WRITER_H__
-#define UPDATE_ENGINE_MOCK_FILE_WRITER_H__
+#ifndef CHROMEOS_PLATFORM_UPDATE_ENGINE_MOCK_FILE_WRITER_H__
+#define CHROMEOS_PLATFORM_UPDATE_ENGINE_MOCK_FILE_WRITER_H__
 
 #include "base/basictypes.h"
 #include "update_engine/file_writer.h"
@@ -56,4 +56,4 @@
 
 }  // namespace chromeos_update_engine
 
-#endif  // UPDATE_ENGINE_MOCK_FILE_WRITER_H__
+#endif  // CHROMEOS_PLATFORM_UPDATE_ENGINE_MOCK_FILE_WRITER_H__
diff --git a/mock_http_fetcher.cc b/mock_http_fetcher.cc
index 6459351..abeff7a 100644
--- a/mock_http_fetcher.cc
+++ b/mock_http_fetcher.cc
@@ -2,11 +2,14 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "base/logging.h"
 #include "update_engine/mock_http_fetcher.h"
+#include <algorithm>
+#include "chromeos/obsolete_logging.h"
 
 // This is a mac implementation of HttpFetcher which is useful for testing.
 
+using std::min;
+
 namespace chromeos_update_engine {
 
 MockHttpFetcher::~MockHttpFetcher() {
diff --git a/mock_http_fetcher.h b/mock_http_fetcher.h
index 68cb731..2efd258 100644
--- a/mock_http_fetcher.h
+++ b/mock_http_fetcher.h
@@ -2,12 +2,12 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef UPDATE_ENGINE_MOCK_HTTP_FETCHER_H__
-#define UPDATE_ENGINE_MOCK_HTTP_FETCHER_H__
+#ifndef CHROMEOS_PLATFORM_UPDATE_ENGINE_MOCK_HTTP_FETCHER_H__
+#define CHROMEOS_PLATFORM_UPDATE_ENGINE_MOCK_HTTP_FETCHER_H__
 
 #include <vector>
 #include <glib.h>
-#include <base/logging.h>
+#include "chromeos/obsolete_logging.h"
 #include "update_engine/http_fetcher.h"
 
 // This is a mock implementation of HttpFetcher which is useful for testing.
@@ -54,7 +54,7 @@
   const std::vector<char>& post_data() const {
     return post_data_;
   }
-  
+
  private:
   // Sends data to the delegate and sets up a glib timeout callback if needed.
   // There must be a delegate and there must be data to send. If there is
@@ -91,4 +91,4 @@
 
 }  // namespace chromeos_update_engine
 
-#endif  // UPDATE_ENGINE_MOCK_HTTP_FETCHER_H__
+#endif  // CHROMEOS_PLATFORM_UPDATE_ENGINE_MOCK_HTTP_FETCHER_H__
diff --git a/omaha_hash_calculator.cc b/omaha_hash_calculator.cc
index 2ef0f97..93b3d9e 100644
--- a/omaha_hash_calculator.cc
+++ b/omaha_hash_calculator.cc
@@ -5,13 +5,13 @@
 #include <openssl/bio.h>
 #include <openssl/buffer.h>
 #include <openssl/evp.h>
-#include "base/logging.h"
+#include "chromeos/obsolete_logging.h"
 #include "update_engine/omaha_hash_calculator.h"
 
 namespace chromeos_update_engine {
 
 OmahaHashCalculator::OmahaHashCalculator() {
-  CHECK_EQ(1, SHA1_Init(&ctx_));
+  CHECK_EQ(SHA1_Init(&ctx_), 1);
 }
 
 // Update is called with all of the data that should be hashed in order.
@@ -20,7 +20,7 @@
   CHECK(hash_.empty()) << "Can't Update after hash is finalized";
   COMPILE_ASSERT(sizeof(size_t) <= sizeof(unsigned long),
                  length_param_may_be_truncated_in_SHA1_Update);
-  CHECK_EQ(1, SHA1_Update(&ctx_, data, length));
+  CHECK_EQ(SHA1_Update(&ctx_, data, length), 1);
 }
 
 // Call Finalize() when all data has been passed in. This mostly just
@@ -28,7 +28,7 @@
 void OmahaHashCalculator::Finalize() {
   CHECK(hash_.empty()) << "Don't call Finalize() twice";
   unsigned char md[SHA_DIGEST_LENGTH];
-  CHECK_EQ(1, SHA1_Final(md, &ctx_));
+  CHECK_EQ(SHA1_Final(md, &ctx_), 1);
 
   // Convert md to base64 encoding and store it in hash_
   BIO *b64 = BIO_new(BIO_f_base64());
@@ -36,8 +36,8 @@
   BIO *bmem = BIO_new(BIO_s_mem());
   CHECK(bmem);
   b64 = BIO_push(b64, bmem);
-  CHECK_EQ(sizeof(md), BIO_write(b64, md, sizeof(md)));
-  CHECK_EQ(1, BIO_flush(b64));
+  CHECK_EQ(BIO_write(b64, md, sizeof(md)), sizeof(md));
+  CHECK_EQ(BIO_flush(b64), 1);
 
   BUF_MEM *bptr = NULL;
   BIO_get_mem_ptr(b64, &bptr);
diff --git a/omaha_hash_calculator.h b/omaha_hash_calculator.h
index 28602a3..c96f75e 100644
--- a/omaha_hash_calculator.h
+++ b/omaha_hash_calculator.h
@@ -2,11 +2,12 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef UPDATE_ENGINE_OMAHA_HASH_CALCULATOR_H__
-#define UPDATE_ENGINE_OMAHA_HASH_CALCULATOR_H__
+#ifndef CHROMEOS_PLATFORM_UPDATE_ENGINE_OMAHA_HASH_CALCULATOR_H__
+#define CHROMEOS_PLATFORM_UPDATE_ENGINE_OMAHA_HASH_CALCULATOR_H__
 
 #include "base/basictypes.h"
 #include <string>
+#include <vector>
 #include <openssl/sha.h>
 
 // Omaha uses base64 encoded SHA-1 as the hash. This class provides a simple
@@ -53,4 +54,4 @@
 
 }  // namespace chromeos_update_engine
 
-#endif  // UPDATE_ENGINE_OMAHA_HASH_CALCULATOR_H__
\ No newline at end of file
+#endif  // CHROMEOS_PLATFORM_UPDATE_ENGINE_OMAHA_HASH_CALCULATOR_H__
\ No newline at end of file
diff --git a/test_utils.cc b/test_utils.cc
index 6255119..6346bf3 100644
--- a/test_utils.cc
+++ b/test_utils.cc
@@ -2,72 +2,273 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+#include "update_engine/test_utils.h"
 #include <sys/stat.h>
 #include <sys/types.h>
+#include <errno.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <unistd.h>
+#include <set>
 #include <string>
 #include <vector>
-#include "update_engine/test_utils.h"
-#include "base/logging.h"
+#include "base/string_util.h"
+#include "chromeos/obsolete_logging.h"
 
 #include "update_engine/file_writer.h"
+#include "update_engine/filesystem_iterator.h"
+#include "update_engine/utils.h"
 
+using std::set;
 using std::string;
 using std::vector;
 
 namespace chromeos_update_engine {
 
-vector<char> ReadFile(const string& path) {
-  vector<char> ret;
-  FILE* fp = fopen(path.c_str(), "r");
-  if (!fp)
-    return ret;
-  const size_t kChunkSize(1024);
-  size_t read_size;
-  do {
-    char buf[kChunkSize];
-    read_size = fread(buf, 1, kChunkSize, fp);
-    ret.insert(ret.end(), buf, buf + read_size);
-  } while (read_size == kChunkSize);
-  fclose(fp);
-  return ret;
+bool WriteFile(const char* path, const char* data, int data_len) {
+  DirectFileWriter writer;
+  TEST_AND_RETURN_FALSE_ERRNO(0 == writer.Open(path,
+                                               O_WRONLY | O_CREAT | O_TRUNC,
+                                               0666));
+  ScopedFileWriterCloser closer(&writer);
+  TEST_AND_RETURN_FALSE_ERRNO(data_len == writer.Write(data, data_len));
+  return true;
 }
 
-bool WriteFile(const std::string& path, const std::vector<char>& data) {
-  DirectFileWriter writer;
-  if (0 != writer.Open(path.c_str(), O_WRONLY | O_CREAT | O_TRUNC, 0644)) {
-    return false;
-  }
-  if (static_cast<int>(data.size()) != writer.Write(&data[0], data.size())) {
-    writer.Close();
-    return false;
-  }
-  writer.Close();
-  return true;
+bool WriteFileVector(const std::string& path, const std::vector<char>& data) {
+  return WriteFile(path.c_str(), &data[0], data.size());
+}
+
+bool WriteFileString(const std::string& path, const std::string& data) {
+  return WriteFile(path.c_str(), data.data(), data.size());
 }
 
 off_t FileSize(const string& path) {
   struct stat stbuf;
   int rc = stat(path.c_str(), &stbuf);
-  CHECK_EQ(0, rc);
+  CHECK_EQ(rc, 0);
   if (rc < 0)
     return rc;
   return stbuf.st_size;
 }
 
+std::string Readlink(const std::string& path) {
+  vector<char> buf(PATH_MAX + 1);
+  ssize_t r = readlink(path.c_str(), &buf[0], buf.size());
+  if (r < 0)
+    return "";
+  CHECK_LT(r, static_cast<ssize_t>(buf.size()));
+  buf.resize(r);
+  string ret;
+  ret.insert(ret.begin(), buf.begin(), buf.end());
+  return ret;
+}
+
 std::vector<char> GzipCompressData(const std::vector<char>& data) {
   const char fname[] = "/tmp/GzipCompressDataTemp";
-  if (!WriteFile(fname, data)) {
+  if (!WriteFileVector(fname, data)) {
     system((string("rm ") + fname).c_str());
     return vector<char>();
   }
   system((string("cat ") + fname + "|gzip>" + fname + ".gz").c_str());
   system((string("rm ") + fname).c_str());
-  vector<char> ret = ReadFile(string(fname) + ".gz");
+  vector<char> ret;
+  EXPECT_TRUE(utils::ReadFile(string(fname) + ".gz", &ret));
   system((string("rm ") + fname + ".gz").c_str());
   return ret;
 }
 
+vector<char> GenerateSampleMbr() {
+  // This is the actual MBR from my dev machine. Partition 1 (the first)
+  // is currently marked bootable
+  char mbr[512] = {
+    0xeb, 0x48, 0x90, 0x10, 0x8e, 0xd0, 0xbc, 0x00,
+    0xb0, 0xb8, 0x00, 0x00, 0x8e, 0xd8, 0x8e, 0xc0,
+    0xfb, 0xbe, 0x00, 0x7c, 0xbf, 0x00, 0x06, 0xb9,
+    0x00, 0x02, 0xf3, 0xa4, 0xea, 0x21, 0x06, 0x00,
+    0x00, 0xbe, 0xbe, 0x07, 0x38, 0x04, 0x75, 0x0b,
+    0x83, 0xc6, 0x10, 0x81, 0xfe, 0xfe, 0x07, 0x75,
+    0xf3, 0xeb, 0x16, 0xb4, 0x02, 0xb0, 0x01, 0xbb,
+    0x00, 0x7c, 0xb2, 0x80, 0x8a, 0x74, 0x03, 0x02,
+    0xff, 0x00, 0x00, 0x20, 0x01, 0x00, 0x00, 0x00,
+    0x00, 0x02, 0xfa, 0x90, 0x90, 0xf6, 0xc2, 0x80,
+    0x75, 0x02, 0xb2, 0x80, 0xea, 0x59, 0x7c, 0x00,
+    0x00, 0x31, 0xc0, 0x8e, 0xd8, 0x8e, 0xd0, 0xbc,
+    0x00, 0x20, 0xfb, 0xa0, 0x40, 0x7c, 0x3c, 0xff,
+    0x74, 0x02, 0x88, 0xc2, 0x52, 0xbe, 0x7f, 0x7d,
+    0xe8, 0x34, 0x01, 0xf6, 0xc2, 0x80, 0x74, 0x54,
+    0xb4, 0x41, 0xbb, 0xaa, 0x55, 0xcd, 0x13, 0x5a,
+    0x52, 0x72, 0x49, 0x81, 0xfb, 0x55, 0xaa, 0x75,
+    0x43, 0xa0, 0x41, 0x7c, 0x84, 0xc0, 0x75, 0x05,
+    0x83, 0xe1, 0x01, 0x74, 0x37, 0x66, 0x8b, 0x4c,
+    0x10, 0xbe, 0x05, 0x7c, 0xc6, 0x44, 0xff, 0x01,
+    0x66, 0x8b, 0x1e, 0x44, 0x7c, 0xc7, 0x04, 0x10,
+    0x00, 0xc7, 0x44, 0x02, 0x01, 0x00, 0x66, 0x89,
+    0x5c, 0x08, 0xc7, 0x44, 0x06, 0x00, 0x70, 0x66,
+    0x31, 0xc0, 0x89, 0x44, 0x04, 0x66, 0x89, 0x44,
+    0x0c, 0xb4, 0x42, 0xcd, 0x13, 0x72, 0x05, 0xbb,
+    0x00, 0x70, 0xeb, 0x7d, 0xb4, 0x08, 0xcd, 0x13,
+    0x73, 0x0a, 0xf6, 0xc2, 0x80, 0x0f, 0x84, 0xea,
+    0x00, 0xe9, 0x8d, 0x00, 0xbe, 0x05, 0x7c, 0xc6,
+    0x44, 0xff, 0x00, 0x66, 0x31, 0xc0, 0x88, 0xf0,
+    0x40, 0x66, 0x89, 0x44, 0x04, 0x31, 0xd2, 0x88,
+    0xca, 0xc1, 0xe2, 0x02, 0x88, 0xe8, 0x88, 0xf4,
+    0x40, 0x89, 0x44, 0x08, 0x31, 0xc0, 0x88, 0xd0,
+    0xc0, 0xe8, 0x02, 0x66, 0x89, 0x04, 0x66, 0xa1,
+    0x44, 0x7c, 0x66, 0x31, 0xd2, 0x66, 0xf7, 0x34,
+    0x88, 0x54, 0x0a, 0x66, 0x31, 0xd2, 0x66, 0xf7,
+    0x74, 0x04, 0x88, 0x54, 0x0b, 0x89, 0x44, 0x0c,
+    0x3b, 0x44, 0x08, 0x7d, 0x3c, 0x8a, 0x54, 0x0d,
+    0xc0, 0xe2, 0x06, 0x8a, 0x4c, 0x0a, 0xfe, 0xc1,
+    0x08, 0xd1, 0x8a, 0x6c, 0x0c, 0x5a, 0x8a, 0x74,
+    0x0b, 0xbb, 0x00, 0x70, 0x8e, 0xc3, 0x31, 0xdb,
+    0xb8, 0x01, 0x02, 0xcd, 0x13, 0x72, 0x2a, 0x8c,
+    0xc3, 0x8e, 0x06, 0x48, 0x7c, 0x60, 0x1e, 0xb9,
+    0x00, 0x01, 0x8e, 0xdb, 0x31, 0xf6, 0x31, 0xff,
+    0xfc, 0xf3, 0xa5, 0x1f, 0x61, 0xff, 0x26, 0x42,
+    0x7c, 0xbe, 0x85, 0x7d, 0xe8, 0x40, 0x00, 0xeb,
+    0x0e, 0xbe, 0x8a, 0x7d, 0xe8, 0x38, 0x00, 0xeb,
+    0x06, 0xbe, 0x94, 0x7d, 0xe8, 0x30, 0x00, 0xbe,
+    0x99, 0x7d, 0xe8, 0x2a, 0x00, 0xeb, 0xfe, 0x47,
+    0x52, 0x55, 0x42, 0x20, 0x00, 0x47, 0x65, 0x6f,
+    0x6d, 0x00, 0x48, 0x61, 0x72, 0x64, 0x20, 0x44,
+    0x69, 0x73, 0x6b, 0x00, 0x52, 0x65, 0x61, 0x64,
+    0x00, 0x20, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x00,
+    0xbb, 0x01, 0x00, 0xb4, 0x0e, 0xcd, 0x10, 0xac,
+    0x3c, 0x00, 0x75, 0xf4, 0xc3, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x50, 0xc1, 0x04, 0x00, 0x00, 0x00, 0x80, 0x01,
+    0x01, 0x00, 0x83, 0xfe, 0xff, 0xff, 0x3f, 0x00,
+    0x00, 0x00, 0x09, 0x7f, 0x32, 0x06, 0x00, 0xfe,
+    0xff, 0xff, 0x05, 0xfe, 0xff, 0xff, 0x48, 0x7f,
+    0x32, 0x06, 0x79, 0x59, 0x2d, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x55, 0xaa
+  };
+  vector<char> ret;
+  ret.insert(ret.begin(), mbr, mbr + sizeof(mbr));
+  return ret;
+}
+
+std::string GetUnusedLoopDevice() {
+  // get a loop device we can use for the install device
+  FILE* find_dev_cmd = popen("losetup -f", "r");
+  CHECK(find_dev_cmd);
+
+  string ret;
+  char dev[100] = {0};
+  size_t r;
+  while ((r = fread(dev, 1, sizeof(dev - 1), find_dev_cmd)) > 0) {
+    EXPECT_LT(r, sizeof(dev));
+    ret.insert(ret.end(), dev, dev + r);
+  }
+  EXPECT_EQ(r, 0);
+  EXPECT_TRUE(feof(find_dev_cmd));
+  fclose(find_dev_cmd);
+
+  // strip trailing \n on dev
+  if (*ret.rbegin() == '\n')
+    ret.resize(ret.size() - 1);
+
+  return ret;
+}
+
+bool ExpectVectorsEq(const vector<char>& a, const vector<char>& b) {
+  EXPECT_EQ(a.size(), b.size());
+  if (a.size() != b.size())
+    return false;
+  for (unsigned int i = 0; i < a.size(); i++) {
+    EXPECT_EQ(a[i], b[i]) << "offset: " << i;
+  }
+  return true;
+}
+
+void CreateExtImageAtPath(const string& path, vector<string>* out_paths) {
+  // create 10MiB sparse file
+  const char* const mount_path = kMountPath.c_str();
+  EXPECT_EQ(0, System(StringPrintf("dd if=/dev/zero of=%s"
+                                   " seek=10485759 bs=1 count=1",
+                                   path.c_str())));
+  EXPECT_EQ(0, System(StringPrintf("mkfs.ext3 -F %s", path.c_str())));
+  EXPECT_EQ(0, System(StringPrintf("mkdir -p %s", mount_path)));
+  EXPECT_EQ(0, System(StringPrintf("mount -o loop %s %s", path.c_str(),
+                                   mount_path)));
+  EXPECT_EQ(0, System(StringPrintf("echo hi > %s/hi", mount_path)));
+  EXPECT_EQ(0, System(StringPrintf("echo hello > %s/hello", mount_path)));
+  EXPECT_EQ(0, System(StringPrintf("mkdir %s/some_dir", mount_path)));
+  EXPECT_EQ(0, System(StringPrintf("mkdir %s/some_dir/empty_dir", mount_path)));
+  EXPECT_EQ(0, System(StringPrintf("mkdir %s/some_dir/mnt", mount_path)));
+  EXPECT_EQ(0, System(StringPrintf("echo T > %s/some_dir/test", mount_path)));
+  EXPECT_EQ(0, System(StringPrintf("mkfifo %s/some_dir/fifo", mount_path)));
+  EXPECT_EQ(0, System(StringPrintf("mknod %s/cdev c 2 3", mount_path)));
+  EXPECT_EQ(0, System(StringPrintf("ln -s /some/target %s/sym", mount_path)));
+  EXPECT_EQ(0, System(StringPrintf("ln %s/some_dir/test %s/testlink",
+                                   mount_path, mount_path)));
+  EXPECT_EQ(0, System(StringPrintf("umount %s", mount_path)));
+  
+  if (out_paths) {
+    out_paths->clear();
+    out_paths->push_back("");
+    out_paths->push_back("/hi");
+    out_paths->push_back("/hello");
+    out_paths->push_back("/some_dir");
+    out_paths->push_back("/some_dir/empty_dir");
+    out_paths->push_back("/some_dir/mnt");
+    out_paths->push_back("/some_dir/test");
+    out_paths->push_back("/some_dir/fifo");
+    out_paths->push_back("/cdev");
+    out_paths->push_back("/testlink");
+    out_paths->push_back("/sym");
+    out_paths->push_back("/lost+found");
+  }
+}
+
+void VerifyAllPaths(const string& parent, set<string> expected_paths) {
+  FilesystemIterator iter(parent, set<string>());
+  ino_t test_ino = 0;
+  ino_t testlink_ino = 0;
+  while (!iter.IsEnd()) {
+    string path = iter.GetFullPath();
+    EXPECT_TRUE(expected_paths.find(path) != expected_paths.end()) << path;
+    EXPECT_EQ(1, expected_paths.erase(path));
+    if (utils::StringHasSuffix(path, "/hi") ||
+        utils::StringHasSuffix(path, "/hello") ||
+        utils::StringHasSuffix(path, "/test") ||
+        utils::StringHasSuffix(path, "/testlink")) {
+      EXPECT_TRUE(S_ISREG(iter.GetStat().st_mode));
+      if (utils::StringHasSuffix(path, "/test"))
+        test_ino = iter.GetStat().st_ino;
+      else if (utils::StringHasSuffix(path, "/testlink"))
+        testlink_ino = iter.GetStat().st_ino;
+    } else if (utils::StringHasSuffix(path, "/some_dir") ||
+               utils::StringHasSuffix(path, "/empty_dir") ||
+               utils::StringHasSuffix(path, "/mnt") ||
+               utils::StringHasSuffix(path, "/lost+found") ||
+               parent == path) {
+      EXPECT_TRUE(S_ISDIR(iter.GetStat().st_mode));
+    } else if (utils::StringHasSuffix(path, "/fifo")) {
+      EXPECT_TRUE(S_ISFIFO(iter.GetStat().st_mode));
+    } else if (utils::StringHasSuffix(path, "/cdev")) {
+      EXPECT_TRUE(S_ISCHR(iter.GetStat().st_mode));
+    } else if (utils::StringHasSuffix(path, "/sym")) {
+      EXPECT_TRUE(S_ISLNK(iter.GetStat().st_mode));
+    } else {
+      LOG(INFO) << "got non hardcoded path: " << path;
+    }
+    iter.Increment();
+  }
+  EXPECT_EQ(testlink_ino, test_ino);
+  EXPECT_NE(0, test_ino);
+  EXPECT_FALSE(iter.IsErr());
+  EXPECT_TRUE(expected_paths.empty());
+  if (!expected_paths.empty()) {
+    for (set<string>::const_iterator it = expected_paths.begin();
+         it != expected_paths.end(); ++it) {
+      LOG(INFO) << "extra path: " << *it;
+    }
+  }
+}
+
 }  // namespace chromeos_update_engine
diff --git a/test_utils.h b/test_utils.h
index 0c12e13..642158b 100644
--- a/test_utils.h
+++ b/test_utils.h
@@ -2,32 +2,178 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef UPDATE_ENGINE_TEST_UTILS_H__
-#define UPDATE_ENGINE_TEST_UTILS_H__
+#ifndef CHROMEOS_PLATFORM_UPDATE_ENGINE_TEST_UTILS_H__
+#define CHROMEOS_PLATFORM_UPDATE_ENGINE_TEST_UTILS_H__
 
-#include <vector>
+#include <set>
 #include <string>
+#include <vector>
+#include <gtest/gtest.h>
+#include "update_engine/action.h"
 
 // These are some handy functions for unittests.
 
 namespace chromeos_update_engine {
 
-// Returns the entire contents of the file at path. If the file doesn't
-// exist or error occurrs, an empty vector is returned.
-std::vector<char> ReadFile(const std::string& path);
-
 // Writes the data passed to path. The file at path will be overwritten if it
 // exists. Returns true on success, false otherwise.
-bool WriteFile(const std::string& path, const std::vector<char>& data);
+bool WriteFile(const char* path, const char* data, int data_len);
+bool WriteFileVector(const std::string& path, const std::vector<char>& data);
+bool WriteFileString(const std::string& path, const std::string& data);
 
 // Returns the size of the file at path. If the file doesn't exist or some
 // error occurrs, -1 is returned.
 off_t FileSize(const std::string& path);
 
+// Reads a symlink from disk. Returns empty string on failure.
+std::string Readlink(const std::string& path);
+
 // Gzip compresses the data passed using the gzip command line program.
 // Returns compressed data back.
 std::vector<char> GzipCompressData(const std::vector<char>& data);
 
+// Gives back a 512-bytes length array that contains an MBR with
+// the first partition is marked bootable.
+std::vector<char> GenerateSampleMbr();
+
+std::string GetUnusedLoopDevice();
+
+// Returns true iff a == b
+bool ExpectVectorsEq(const std::vector<char>& a, const std::vector<char>& b);
+
+inline int System(const std::string& cmd) {
+  return system(cmd.c_str());
+}
+
+namespace {
+// 300 byte pseudo-random string. Not null terminated.
+// This does not gzip compress well.
+const unsigned char kRandomString[] = {
+  0xf2, 0xb7, 0x55, 0x92, 0xea, 0xa6, 0xc9, 0x57,
+  0xe0, 0xf8, 0xeb, 0x34, 0x93, 0xd9, 0xc4, 0x8f,
+  0xcb, 0x20, 0xfa, 0x37, 0x4b, 0x40, 0xcf, 0xdc,
+  0xa5, 0x08, 0x70, 0x89, 0x79, 0x35, 0xe2, 0x3d,
+  0x56, 0xa4, 0x75, 0x73, 0xa3, 0x6d, 0xd1, 0xd5,
+  0x26, 0xbb, 0x9c, 0x60, 0xbd, 0x2f, 0x5a, 0xfa,
+  0xb7, 0xd4, 0x3a, 0x50, 0xa7, 0x6b, 0x3e, 0xfd,
+  0x61, 0x2b, 0x3a, 0x31, 0x30, 0x13, 0x33, 0x53,
+  0xdb, 0xd0, 0x32, 0x71, 0x5c, 0x39, 0xed, 0xda,
+  0xb4, 0x84, 0xca, 0xbc, 0xbd, 0x78, 0x1c, 0x0c,
+  0xd8, 0x0b, 0x41, 0xe8, 0xe1, 0xe0, 0x41, 0xad,
+  0x03, 0x12, 0xd3, 0x3d, 0xb8, 0x75, 0x9b, 0xe6,
+  0xd9, 0x01, 0xd0, 0x87, 0xf4, 0x36, 0xfa, 0xa7,
+  0x0a, 0xfa, 0xc5, 0x87, 0x65, 0xab, 0x9a, 0x7b,
+  0xeb, 0x58, 0x23, 0xf0, 0xa8, 0x0a, 0xf2, 0x33,
+  0x3a, 0xe2, 0xe3, 0x35, 0x74, 0x95, 0xdd, 0x3c,
+  0x59, 0x5a, 0xd9, 0x52, 0x3a, 0x3c, 0xac, 0xe5,
+  0x15, 0x87, 0x6d, 0x82, 0xbc, 0xf8, 0x7d, 0xbe,
+  0xca, 0xd3, 0x2c, 0xd6, 0xec, 0x38, 0xeb, 0xe4,
+  0x53, 0xb0, 0x4c, 0x3f, 0x39, 0x29, 0xf7, 0xa4,
+  0x73, 0xa8, 0xcb, 0x32, 0x50, 0x05, 0x8c, 0x1c,
+  0x1c, 0xca, 0xc9, 0x76, 0x0b, 0x8f, 0x6b, 0x57,
+  0x1f, 0x24, 0x2b, 0xba, 0x82, 0xba, 0xed, 0x58,
+  0xd8, 0xbf, 0xec, 0x06, 0x64, 0x52, 0x6a, 0x3f,
+  0xe4, 0xad, 0xce, 0x84, 0xb4, 0x27, 0x55, 0x14,
+  0xe3, 0x75, 0x59, 0x73, 0x71, 0x51, 0xea, 0xe8,
+  0xcc, 0xda, 0x4f, 0x09, 0xaf, 0xa4, 0xbc, 0x0e,
+  0xa6, 0x1f, 0xe2, 0x3a, 0xf8, 0x96, 0x7d, 0x30,
+  0x23, 0xc5, 0x12, 0xb5, 0xd8, 0x73, 0x6b, 0x71,
+  0xab, 0xf1, 0xd7, 0x43, 0x58, 0xa7, 0xc9, 0xf0,
+  0xe4, 0x85, 0x1c, 0xd6, 0x92, 0x50, 0x2c, 0x98,
+  0x36, 0xfe, 0x87, 0xaf, 0x43, 0x8f, 0x8f, 0xf5,
+  0x88, 0x48, 0x18, 0x42, 0xcf, 0x42, 0xc1, 0xa8,
+  0xe8, 0x05, 0x08, 0xa1, 0x45, 0x70, 0x5b, 0x8c,
+  0x39, 0x28, 0xab, 0xe9, 0x6b, 0x51, 0xd2, 0xcb,
+  0x30, 0x04, 0xea, 0x7d, 0x2f, 0x6e, 0x6c, 0x3b,
+  0x5f, 0x82, 0xd9, 0x5b, 0x89, 0x37, 0x65, 0x65,
+  0xbe, 0x9f, 0xa3, 0x5d
+};
+
+const string kMountPath("/tmp/UpdateEngineTests_mnt");
+}  // namespace {}
+
+// Creates an ext image with some files in it. The paths creates are
+// returned in out_paths.
+void CreateExtImageAtPath(const std::string& path,
+                          std::vector<std::string>* out_paths);
+
+// Verifies that for each path in paths, it exists in the filesystem under
+// parent. Also, verifies that no additional paths are present under parent.
+// Also tests properties of various files created by CreateExtImageAtPath().
+// Intentionally copies expected_paths.
+void VerifyAllPaths(const std::string& parent,
+                    std::set<std::string> expected_paths);
+
+// Useful actions for test
+
+class NoneType;
+
+template<typename T>
+class ObjectFeederAction;
+
+template<typename T>
+class ActionTraits<ObjectFeederAction<T> > {
+ public:
+  typedef T OutputObjectType;
+  typedef NoneType InputObjectType;
+};
+
+// This is a simple Action class for testing. It feeds an object into
+// another action.
+template<typename T>
+struct ObjectFeederAction : public Action<ObjectFeederAction<T> > {
+ public:
+  typedef NoneType InputObjectType;
+  typedef T OutputObjectType;
+  void PerformAction() {
+    LOG(INFO) << "feeder running!";
+    CHECK(this->processor_);
+    if (this->HasOutputPipe()) {
+      this->SetOutputObject(out_obj_);
+    }
+    this->processor_->ActionComplete(this, true);
+  }
+  static std::string StaticType() { return "ObjectFeederAction"; }
+  std::string Type() const { return StaticType(); }
+  void set_obj(const T& out_obj) {
+    out_obj_ = out_obj;
+  }
+ private:
+  T out_obj_;
+};
+
+template<typename T>
+class ObjectCollectorAction;
+
+template<typename T>
+class ActionTraits<ObjectCollectorAction<T> > {
+ public:
+  typedef NoneType OutputObjectType;
+  typedef T InputObjectType;
+};
+
+// This is a simple Action class for testing. It receives an object from
+// another action.
+template<typename T>
+struct ObjectCollectorAction : public Action<ObjectCollectorAction<T> > {
+ public:
+  typedef T InputObjectType;
+  typedef NoneType OutputObjectType;
+  void PerformAction() {
+    LOG(INFO) << "collector running!";
+    ASSERT_TRUE(this->processor_);
+    if (this->HasInputObject()) {
+      object_ = this->GetInputObject();
+    }
+    this->processor_->ActionComplete(this, true);
+  }
+  static std::string StaticType() { return "ObjectCollectorAction"; }
+  std::string Type() const { return StaticType(); }
+  const T& object() const { return object_; }
+ private:
+  T object_;
+};
+
 }  // namespace chromeos_update_engine
 
-#endif  // UPDATE_ENGINE_TEST_UTILS_H__
\ No newline at end of file
+#endif  // CHROMEOS_PLATFORM_UPDATE_ENGINE_TEST_UTILS_H__
\ No newline at end of file
diff --git a/testrunner.cc b/testrunner.cc
index 8931902..0a74480 100644
--- a/testrunner.cc
+++ b/testrunner.cc
@@ -4,9 +4,14 @@
 
 // based on pam_google_testrunner.cc
 
+#include <glib.h>
 #include <gtest/gtest.h>
 
+#include "update_engine/subprocess.h"
+
 int main(int argc, char **argv) {
+  g_thread_init(NULL);
+  chromeos_update_engine::Subprocess::Init();
   ::testing::InitGoogleTest(&argc, argv);
   return RUN_ALL_TESTS();
 }
diff --git a/update_check_action.cc b/update_check_action.cc
index a4942a6..a9d303c 100644
--- a/update_check_action.cc
+++ b/update_check_action.cc
@@ -9,14 +9,16 @@
 #include <libxml/xpath.h>
 #include <libxml/xpathInternals.h>
 
+#include "chromeos/obsolete_logging.h"
 #include "update_engine/action_pipe.h"
+#include "update_engine/utils.h"
 
 using std::string;
 
 namespace chromeos_update_engine {
 
 const char* const UpdateCheckParams::kAppId(
-    "87efface-864d-49a5-9bb3-4b050a7c227a");
+    "{87efface-864d-49a5-9bb3-4b050a7c227a}");
 const char* const UpdateCheckParams::kOsPlatform("Chrome OS");
 const char* const UpdateCheckParams::kOsVersion("Indy");
 
@@ -91,13 +93,14 @@
   return string(reinterpret_cast<const char *>(str.get()));
 }
 
-UpdateCheckAction::UpdateCheckAction(const UpdateCheckParams& params,
-                                     HttpFetcher* http_fetcher)
-    : params_(params), http_fetcher_(http_fetcher) {}
+UpdateCheckAction::UpdateCheckAction(HttpFetcher* http_fetcher)
+    : http_fetcher_(http_fetcher) {}
 
 UpdateCheckAction::~UpdateCheckAction() {}
 
 void UpdateCheckAction::PerformAction() {
+  CHECK(HasInputObject());
+  params_ = GetInputObject();
   http_fetcher_->set_delegate(this);
   string request_post(FormatRequest(params_));
   http_fetcher_->SetPostData(request_post.data(), request_post.size());
@@ -118,26 +121,6 @@
 }
 
 namespace {
-// A little object to call ActionComplete on the ActionProcessor when
-// it's destructed.
-class ScopedActionCompleter {
- public:
-  explicit ScopedActionCompleter(ActionProcessor* processor,
-                                 AbstractAction* action)
-      : processor_(processor), action_(action), success_(false) {}
-  ~ScopedActionCompleter() {
-    processor_->ActionComplete(action_, success_);
-  }
-  void set_success(bool success) {
-    success_ = success;
-  }
- private:
-  ActionProcessor* processor_;
-  AbstractAction* action_;
-  bool success_;
-  DISALLOW_COPY_AND_ASSIGN(ScopedActionCompleter);
-};
-
 // If non-NULL response, caller is responsible for calling xmlXPathFreeObject()
 // on the returned object.
 // This code is roughly based on the libxml tutorial at:
diff --git a/update_check_action.h b/update_check_action.h
index cf6d512..402943b 100644
--- a/update_check_action.h
+++ b/update_check_action.h
@@ -2,8 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef UPDATE_ENGINE_UPDATE_CHECK_ACTION_H__
-#define UPDATE_ENGINE_UPDATE_CHECK_ACTION_H__
+#ifndef CHROMEOS_PLATFORM_UPDATE_ENGINE_UPDATE_CHECK_ACTION_H__
+#define CHROMEOS_PLATFORM_UPDATE_ENGINE_UPDATE_CHECK_ACTION_H__
 
 #include <sys/types.h>
 #include <sys/stat.h>
@@ -93,8 +93,8 @@
 template<>
 class ActionTraits<UpdateCheckAction> {
  public:
-  // Does not take an object for input
-  typedef NoneType InputObjectType;
+  // Takes parameters on the input pipe
+  typedef UpdateCheckParams InputObjectType;
   // On success, puts the output path on output
   typedef UpdateCheckResponse OutputObjectType;
 };
@@ -108,8 +108,7 @@
   // Takes ownership of the passed in HttpFetcher. Useful for testing.
   // A good calling pattern is:
   // UpdateCheckAction(..., new WhateverHttpFetcher);
-  UpdateCheckAction(const UpdateCheckParams& params,
-                    HttpFetcher* http_fetcher);
+  UpdateCheckAction(HttpFetcher* http_fetcher);
   virtual ~UpdateCheckAction();
   typedef ActionTraits<UpdateCheckAction>::InputObjectType InputObjectType;
   typedef ActionTraits<UpdateCheckAction>::OutputObjectType OutputObjectType;
@@ -117,7 +116,8 @@
   void TerminateProcessing();
 
   // Debugging/logging
-  std::string Type() const { return "UpdateCheckAction"; }
+  static std::string StaticType() { return "UpdateCheckAction"; }
+  std::string Type() const { return StaticType(); }
 
   // Delegate methods (see http_fetcher.h)
   virtual void ReceivedBytes(HttpFetcher *fetcher,
@@ -139,4 +139,4 @@
 
 }  // namespace chromeos_update_engine
 
-#endif  // UPDATE_ENGINE_UPDATE_CHECK_ACTION_H__
+#endif  // CHROMEOS_PLATFORM_UPDATE_ENGINE_UPDATE_CHECK_ACTION_H__
diff --git a/update_check_action_unittest.cc b/update_check_action_unittest.cc
index 9b8436c..723fc22 100644
--- a/update_check_action_unittest.cc
+++ b/update_check_action_unittest.cc
@@ -3,6 +3,7 @@
 // found in the LICENSE file.
 
 #include <string>
+#include <vector>
 #include <glib.h>
 #include <gtest/gtest.h>
 #include "update_engine/action_pipe.h"
@@ -11,9 +12,10 @@
 #include "update_engine/omaha_hash_calculator.h"
 #include "update_engine/test_utils.h"
 
-namespace chromeos_update_engine {
-
 using std::string;
+using std::vector;
+
+namespace chromeos_update_engine {
 
 class UpdateCheckActionTest : public ::testing::Test { };
 
@@ -35,8 +37,9 @@
                          const string& needsadmin,
                          const string& size) {
   return string("<?xml version=\"1.0\" encoding=\"UTF-8\"?><gupdate "
-      "xmlns=\"http://www.google.com/update2/response\" protocol=\"2.0\"><app "
-      "appid=\"") + app_id + "\" status=\"ok\"><ping "
+                "xmlns=\"http://www.google.com/update2/response\" "
+                "protocol=\"2.0\"><app "
+                "appid=\"") + app_id + "\" status=\"ok\"><ping "
       "status=\"ok\"/><updatecheck DisplayVersion=\"" + display_version + "\" "
       "MoreInfo=\"" + more_info_url + "\" Prompt=\"" + prompt + "\" "
       "codebase=\"" + codebase + "\" "
@@ -56,11 +59,11 @@
     g_main_loop_quit(loop_);
   }
 
-  virtual void ActionCompleted(const ActionProcessor* processor,
-                               const AbstractAction* action,
+  virtual void ActionCompleted(ActionProcessor* processor,
+                               AbstractAction* action,
                                bool success) {
     // make sure actions always succeed
-    if (action->Type() == "UpdateCheckAction")
+    if (action->Type() == UpdateCheckAction::StaticType())
       EXPECT_EQ(expected_success_, success);
     else
       EXPECT_TRUE(success);
@@ -102,7 +105,10 @@
     CHECK(false);
   }
   // Debugging/logging
-  std::string Type() const { return "OutputObjectCollectorAction"; }
+  static std::string StaticType() {
+    return "OutputObjectCollectorAction";
+  }
+  std::string Type() const { return StaticType(); }
   bool has_input_object_;
   UpdateCheckResponse update_check_response_;
 };
@@ -119,17 +125,20 @@
   GMainLoop *loop = g_main_loop_new(g_main_context_default(), FALSE);
   MockHttpFetcher *fetcher = new MockHttpFetcher(http_response.data(),
                                                  http_response.size());
-
-  UpdateCheckAction action(params, fetcher);  // takes ownership of fetcher
+  ObjectFeederAction<UpdateCheckParams> feeder_action;
+  UpdateCheckAction action(fetcher);  // takes ownership of fetcher
   UpdateCheckActionTestProcessorDelegate delegate;
   delegate.loop_ = loop;
   delegate.expected_success_ = expected_success;
   ActionProcessor processor;
+  feeder_action.set_obj(params);
   processor.set_delegate(&delegate);
+  processor.EnqueueAction(&feeder_action);
   processor.EnqueueAction(&action);
 
   OutputObjectCollectorAction collector_action;
 
+  BondActions(&feeder_action, &action);
   BondActions(&action, &collector_action);
   processor.EnqueueAction(&collector_action);
 
@@ -211,14 +220,17 @@
 
   GMainLoop *loop = g_main_loop_new(g_main_context_default(), FALSE);
 
-  UpdateCheckAction action(params,
-                           new MockHttpFetcher(http_response.data(),
+  ObjectFeederAction<UpdateCheckParams> feeder_action;
+  feeder_action.set_obj(params);
+  UpdateCheckAction action(new MockHttpFetcher(http_response.data(),
                                                http_response.size()));
   UpdateCheckActionTestProcessorDelegate delegate;
   delegate.loop_ = loop;
   ActionProcessor processor;
   processor.set_delegate(&delegate);
+  processor.EnqueueAction(&feeder_action);
   processor.EnqueueAction(&action);
+  BondActions(&feeder_action, &action);
 
   g_timeout_add(0, &StartProcessorInRunLoop, &processor);
   g_main_loop_run(loop);
@@ -327,18 +339,23 @@
                            "unittest_track");
   UpdateCheckResponse response;
   ASSERT_TRUE(TestUpdateCheckAction(params,
-      string("<?xml version=\"1.0\" encoding=\"UTF-8\"?><gupdate "
-          "xmlns=\"http://www.google.com/update2/response\" "
-          "protocol=\"2.0\"><app  appid=\"") +
-          UpdateCheckParams::kAppId + "\" status=\"ok\"><ping "
-          "status=\"ok\"/><updatecheck DisplayVersion=\"1.2.3.4\" "
-          "Prompt=\"false\" "
-          "codebase=\"http://code/base\" "
-          "hash=\"HASH1234=\" needsadmin=\"true\" "
-          "size=\"123\" status=\"ok\"/></app></gupdate>",
-      true,
-      &response,
-      NULL));
+                                    string("<?xml version=\"1.0\" "
+                                           "encoding=\"UTF-8\"?><gupdate "
+                                           "xmlns=\"http://www.google.com/"
+                                           "update2/response\" "
+                                           "protocol=\"2.0\"><app  appid=\"") +
+                                    UpdateCheckParams::kAppId
+                                    + "\" status=\"ok\"><ping "
+                                    "status=\"ok\"/><updatecheck "
+                                    "DisplayVersion=\"1.2.3.4\" "
+                                    "Prompt=\"false\" "
+                                    "codebase=\"http://code/base\" "
+                                    "hash=\"HASH1234=\" needsadmin=\"true\" "
+                                    "size=\"123\" "
+                                    "status=\"ok\"/></app></gupdate>",
+                                    true,
+                                    &response,
+                                    NULL));
   EXPECT_TRUE(response.update_exists);
   EXPECT_EQ("1.2.3.4", response.display_version);
   EXPECT_EQ("http://code/base", response.codebase);
@@ -381,14 +398,17 @@
   string http_response("doesn't matter");
   GMainLoop *loop = g_main_loop_new(g_main_context_default(), FALSE);
 
-  UpdateCheckAction action(params,
-                           new MockHttpFetcher(http_response.data(),
+  ObjectFeederAction<UpdateCheckParams> feeder_action;
+  feeder_action.set_obj(params);
+  UpdateCheckAction action(new MockHttpFetcher(http_response.data(),
                                                http_response.size()));
   TerminateEarlyTestProcessorDelegate delegate;
   delegate.loop_ = loop;
   ActionProcessor processor;
   processor.set_delegate(&delegate);
+  processor.EnqueueAction(&feeder_action);
   processor.EnqueueAction(&action);
+  BondActions(&feeder_action, &action);
 
   g_timeout_add(0, &TerminateTransferTestStarter, &processor);
   g_main_loop_run(loop);