AU: Update Downloader to support our image formats.

The downloader used to be dumb in the sense that it would pipe output
to either a DirectFileWriter or a DirectFileWriter via a
GzipDecompressingFileWriter, depending on if we were downloading an
update that was compressed or not. Sadly, things have gotten more
complex: we need to download to two partitions (kernel + rootfs), and
we may stream data via a DeltaPerformer (a type of FileWriter) to the
disk. Thus, the Downloader streams to either
1. gzip decompress->split_writer->direct to disk OR
2. delta performer

Other misc changes: Change FilesystemCopierAction to support
optionally copying the kernel partition rather than root partition.

InstallPlan struct: add an entry for destiation kernel partition.

Test Utils: a new ScopedTempFile class

Utils: support for getting the booted kernel partition device.

BUG=None
TEST=attached unittests

Review URL: http://codereview.chromium.org/1694025
diff --git a/download_action.cc b/download_action.cc
index 6da6719..5387821 100644
--- a/download_action.cc
+++ b/download_action.cc
@@ -13,44 +13,60 @@
 namespace chromeos_update_engine {
 
 DownloadAction::DownloadAction(HttpFetcher* http_fetcher)
-    : size_(0),
-      should_decompress_(false),
-      writer_(NULL),
+    : writer_(NULL),
       http_fetcher_(http_fetcher) {}
 
 DownloadAction::~DownloadAction() {}
 
 void DownloadAction::PerformAction() {
   http_fetcher_->set_delegate(this);
-  CHECK(!writer_);
-  direct_file_writer_.reset(new DirectFileWriter);
 
   // Get the InstallPlan and read it
   CHECK(HasInputObject());
-  InstallPlan install_plan(GetInputObject());
+  install_plan_ = GetInputObject();
 
-  should_decompress_ = install_plan.is_full_update;
-  url_ = install_plan.download_url;
-  output_path_ = install_plan.install_path;
-  hash_ = install_plan.download_hash;
-  install_plan.Dump();
+  install_plan_.Dump();
 
-  if (should_decompress_) {
-    decompressing_file_writer_.reset(
-        new GzipDecompressingFileWriter(direct_file_writer_.get()));
-    writer_ = decompressing_file_writer_.get();
+  if (writer_) {
+    LOG(INFO) << "Using writer for test.";
   } else {
-    writer_ = direct_file_writer_.get();
+    if (install_plan_.is_full_update) {
+      kernel_file_writer_.reset(new DirectFileWriter);
+      rootfs_file_writer_.reset(new DirectFileWriter);
+      split_file_writer_.reset(new SplitFileWriter(kernel_file_writer_.get(),
+                                                   rootfs_file_writer_.get()));
+      split_file_writer_->SetFirstOpenArgs(
+          install_plan_.kernel_install_path.c_str(),
+          O_WRONLY | O_CREAT | O_TRUNC | O_LARGEFILE,
+          0644);
+      decompressing_file_writer_.reset(
+          new GzipDecompressingFileWriter(split_file_writer_.get()));
+      writer_ = decompressing_file_writer_.get();
+    } else {
+      delta_performer_.reset(new DeltaPerformer);
+      writer_ = delta_performer_.get();
+    }
   }
-  int rc = writer_->Open(output_path_.c_str(),
-                         O_TRUNC | O_WRONLY | O_CREAT | O_LARGEFILE, 0644);
+  int rc = writer_->Open(install_plan_.install_path.c_str(),
+                         O_TRUNC | O_WRONLY | O_CREAT | O_LARGEFILE,
+                         0644);
   if (rc < 0) {
-    LOG(ERROR) << "Unable to open output file " << output_path_;
+    LOG(ERROR) << "Unable to open output file " << install_plan_.install_path;
     // report error to processor
     processor_->ActionComplete(this, false);
     return;
   }
-  http_fetcher_->BeginTransfer(url_);
+  if (!install_plan_.is_full_update) {
+    if (!delta_performer_->OpenKernel(
+            install_plan_.kernel_install_path.c_str())) {
+      LOG(ERROR) << "Unable to open kernel file "
+                 << install_plan_.kernel_install_path.c_str();
+      writer_->Close();
+      processor_->ActionComplete(this, false);
+      return;
+    }
+  }
+  http_fetcher_->BeginTransfer(install_plan_.download_url);
 }
 
 void DownloadAction::TerminateProcessing() {
@@ -76,9 +92,10 @@
   if (successful) {
     // Make sure hash is correct
     omaha_hash_calculator_.Finalize();
-    if (omaha_hash_calculator_.hash() != hash_) {
-      LOG(ERROR) << "Download of " << url_ << " failed. Expect hash "
-                 << hash_ << " but got hash " << omaha_hash_calculator_.hash();
+    if (omaha_hash_calculator_.hash() != install_plan_.download_hash) {
+      LOG(ERROR) << "Download of " << install_plan_.download_url
+                 << " failed. Expect hash " << install_plan_.download_hash
+                 << " but got hash " << omaha_hash_calculator_.hash();
       successful = false;
     }
   }
diff --git a/download_action.h b/download_action.h
index d5ec026..0f375fa 100644
--- a/download_action.h
+++ b/download_action.h
@@ -16,13 +16,20 @@
 #include "base/scoped_ptr.h"
 #include "update_engine/action.h"
 #include "update_engine/decompressing_file_writer.h"
+#include "update_engine/delta_performer.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"
+#include "update_engine/split_file_writer.h"
 
-// The Download Action downloads a requested url to a specified path on disk.
-// The url and output path are determined by the InstallPlan passed in.
+// The Download Action downloads a specified url to disk. The url should
+// point to either a full or delta update. If a full update, the file will
+// be piped into a SplitFileWriter, which will direct it to the kernel
+// and rootfs partitions. If it's a delta update, the destination kernel
+// and rootfs should already contain the source-version that this delta
+// update goes from. In this case, the update will be piped into a
+// DeltaPerformer that will apply the delta to the disk.
 
 namespace chromeos_update_engine {
 
@@ -50,6 +57,11 @@
   void PerformAction();
   void TerminateProcessing();
 
+  // Testing
+  void SetTestFileWriter(FileWriter* writer) {
+    writer_ = writer;
+  }
+
   // Debugging/logging
   static std::string StaticType() { return "DownloadAction"; }
   std::string Type() const { return StaticType(); }
@@ -60,33 +72,23 @@
   virtual void TransferComplete(HttpFetcher *fetcher, bool successful);
 
  private:
-  // Expected size of the file (will be used for progress info)
-  const size_t size_;
-
-  // URL to download
-  std::string url_;
-
-  // Path to save URL to
-  std::string output_path_;
-
-  // Expected hash of the file. The hash must match for this action to
-  // succeed.
-  std::string hash_;
-
-  // Whether the caller requested that we decompress the downloaded data.
-  bool should_decompress_;
+  // The InstallPlan passed in
+  InstallPlan install_plan_;
 
   // The FileWriter that downloaded data should be written to. It will
-  // either point to *decompressing_file_writer_ or *direct_file_writer_.
+  // either point to *decompressing_file_writer_ or *delta_performer_.
   FileWriter* writer_;
 
-  // If non-null, a FileWriter used for gzip decompressing downloaded data
+  // These are used for full updates:
   scoped_ptr<GzipDecompressingFileWriter> decompressing_file_writer_;
+  scoped_ptr<SplitFileWriter> split_file_writer_;
+  scoped_ptr<DirectFileWriter> kernel_file_writer_;
+  scoped_ptr<DirectFileWriter> rootfs_file_writer_;
 
-  // Used to write out the downloaded file
-  scoped_ptr<DirectFileWriter> direct_file_writer_;
+  // Used to apply a delta update:
+  scoped_ptr<DeltaPerformer> delta_performer_;
 
-  // pointer to the HttpFetcher that does the http work
+  // Pointer to the HttpFetcher that does the http work.
   scoped_ptr<HttpFetcher> http_fetcher_;
 
   // Used to find the hash of the bytes downloaded
diff --git a/download_action_unittest.cc b/download_action_unittest.cc
index d2c5cb1..6353584 100644
--- a/download_action_unittest.cc
+++ b/download_action_unittest.cc
@@ -65,32 +65,30 @@
   return FALSE;
 }
 
-void TestWithData(const vector<char>& data, bool compress) {
-  vector<char> use_data;
-  if (compress) {
-    use_data = GzipCompressData(data);
-  } else {
-    use_data = data;
-  }
-
+void TestWithData(const vector<char>& data) {
   GMainLoop *loop = g_main_loop_new(g_main_context_default(), FALSE);
 
   // TODO(adlr): see if we need a different file for build bots
-  const string path("/tmp/DownloadActionTest");
+  ScopedTempFile output_temp_file;
+  DirectFileWriter writer;
+
   // takes ownership of passed in HttpFetcher
-  InstallPlan install_plan(compress, "",
-                           OmahaHashCalculator::OmahaHashOfData(use_data),
-                           path);
+  InstallPlan install_plan(true,
+                           "",
+                           OmahaHashCalculator::OmahaHashOfData(data),
+                           output_temp_file.GetPath(),
+                           "");
   ObjectFeederAction<InstallPlan> feeder_action;
   feeder_action.set_obj(install_plan);
-  DownloadAction download_action(new MockHttpFetcher(&use_data[0],
-                                                     use_data.size()));
+  DownloadAction download_action(new MockHttpFetcher(&data[0],
+                                                     data.size()));
+  download_action.SetTestFileWriter(&writer);
   BondActions(&feeder_action, &download_action);
 
   DownloadActionTestProcessorDelegate delegate;
   delegate.loop_ = loop;
   delegate.expected_data_ = data;
-  delegate.path_ = path;
+  delegate.path_ = output_temp_file.GetPath();
   ActionProcessor processor;
   processor.set_delegate(&delegate);
   processor.EnqueueAction(&feeder_action);
@@ -99,9 +97,6 @@
   g_timeout_add(0, &StartProcessorInRunLoop, &processor);
   g_main_loop_run(loop);
   g_main_loop_unref(loop);
-
-  // remove temp file; don't care if there are errors here
-  unlink(path.c_str());
 }
 }  // namespace {}
 
@@ -109,8 +104,7 @@
   vector<char> small;
   const char* foo = "foo";
   small.insert(small.end(), foo, foo + strlen(foo));
-  TestWithData(small, false);
-  TestWithData(small, true);
+  TestWithData(small);
 }
 
 TEST(DownloadActionTest, LargeTest) {
@@ -123,8 +117,7 @@
     else
       c++;
   }
-  TestWithData(big, false);
-  TestWithData(big, true);
+  TestWithData(big);
 }
 
 namespace {
@@ -153,13 +146,16 @@
   vector<char> data(kMockHttpFetcherChunkSize + kMockHttpFetcherChunkSize / 2);
   memset(&data[0], 0, data.size());
 
-  const string path("/tmp/DownloadActionTest");
+  ScopedTempFile temp_file;
   {
+    DirectFileWriter writer;
+
     // takes ownership of passed in HttpFetcher
     ObjectFeederAction<InstallPlan> feeder_action;
-    InstallPlan install_plan(false, "", "", path);
+    InstallPlan install_plan(true, "", "", temp_file.GetPath(), "");
     feeder_action.set_obj(install_plan);
     DownloadAction download_action(new MockHttpFetcher(&data[0], data.size()));
+    download_action.SetTestFileWriter(&writer);
     TerminateEarlyTestProcessorDelegate delegate;
     delegate.loop_ = loop;
     ActionProcessor processor;
@@ -174,7 +170,8 @@
   }
 
   // 1 or 0 chunks should have come through
-  const off_t resulting_file_size(utils::FileSize(path));
+  const off_t resulting_file_size(utils::FileSize(temp_file.GetPath()));
+  EXPECT_GE(resulting_file_size, 0);
   if (resulting_file_size != 0)
     EXPECT_EQ(kMockHttpFetcherChunkSize, resulting_file_size);
 }
@@ -231,13 +228,18 @@
 TEST(DownloadActionTest, PassObjectOutTest) {
   GMainLoop *loop = g_main_loop_new(g_main_context_default(), FALSE);
 
+  DirectFileWriter writer;
+
   // takes ownership of passed in HttpFetcher
-  InstallPlan install_plan(false, "",
+  InstallPlan install_plan(true,
+                           "",
                            OmahaHashCalculator::OmahaHashOfString("x"),
+                           "/dev/null",
                            "/dev/null");
   ObjectFeederAction<InstallPlan> feeder_action;
   feeder_action.set_obj(install_plan);
   DownloadAction download_action(new MockHttpFetcher("x", 1));
+  download_action.SetTestFileWriter(&writer);
 
   DownloadActionTestAction test_action;
   test_action.expected_input_object_ = install_plan;
@@ -263,12 +265,15 @@
   GMainLoop *loop = g_main_loop_new(g_main_context_default(), FALSE);
 
   const string path("/fake/path/that/cant/be/created/because/of/missing/dirs");
+  DirectFileWriter writer;
 
   // takes ownership of passed in HttpFetcher
-  InstallPlan install_plan(false, "", "", path);
+  InstallPlan install_plan(true, "", "", path, "");
   ObjectFeederAction<InstallPlan> feeder_action;
   feeder_action.set_obj(install_plan);
   DownloadAction download_action(new MockHttpFetcher("x", 1));
+  download_action.SetTestFileWriter(&writer);
+  
   BondActions(&feeder_action, &download_action);
 
   ActionProcessor processor;
diff --git a/filesystem_copier_action.cc b/filesystem_copier_action.cc
index 18bab44..bd44867 100755
--- a/filesystem_copier_action.cc
+++ b/filesystem_copier_action.cc
@@ -47,17 +47,23 @@
     return;
   }
 
-  const string source =
-      copy_source_.empty() ? utils::BootDevice() : copy_source_;
-  LOG(INFO) << "Copying from " << source << " to "
-            << install_plan_.install_path;
+  string source = copy_source_;
+  if (source.empty()) {
+    source = copying_kernel_install_path_ ?
+        utils::BootKernelDevice(utils::BootDevice()) :
+        utils::BootDevice();
+  }
+
+  const string destination = copying_kernel_install_path_ ?
+      install_plan_.kernel_install_path :
+      install_plan_.install_path;
   
   int src_fd = open(source.c_str(), O_RDONLY);
   if (src_fd < 0) {
     PLOG(ERROR) << "Unable to open " << source << " for reading:";
     return;
   }
-  int dst_fd = open(install_plan_.install_path.c_str(),
+  int dst_fd = open(destination.c_str(),
                     O_WRONLY | O_TRUNC | O_CREAT,
                     0644);
   if (dst_fd < 0) {
diff --git a/filesystem_copier_action.h b/filesystem_copier_action.h
index 9e7f060..786c2ab 100644
--- a/filesystem_copier_action.h
+++ b/filesystem_copier_action.h
@@ -32,8 +32,9 @@
 
 class FilesystemCopierAction : public Action<FilesystemCopierAction> {
  public:
-  FilesystemCopierAction()
-      : src_stream_(NULL),
+  explicit FilesystemCopierAction(bool copying_kernel_install_path)
+      : copying_kernel_install_path_(copying_kernel_install_path),
+        src_stream_(NULL),
         dst_stream_(NULL),
         canceller_(NULL),
         read_in_flight_(false),
@@ -69,6 +70,10 @@
   // was_cancelled should be true if TerminateProcessing() was called.
   void Cleanup(bool success, bool was_cancelled);
   
+  // If true, this action is copying to the kernel_install_path from
+  // the install plan, otherwise it's copying just to the install_path.
+  const bool copying_kernel_install_path_;
+  
   // The path to copy from. If empty (the default), the source is from the
   // passed in InstallPlan.
   std::string copy_source_;
diff --git a/filesystem_copier_action_unittest.cc b/filesystem_copier_action_unittest.cc
index 3ccea2b..6b43817 100644
--- a/filesystem_copier_action_unittest.cc
+++ b/filesystem_copier_action_unittest.cc
@@ -22,7 +22,9 @@
 
 class FilesystemCopierActionTest : public ::testing::Test {
  protected:
-  void DoTest(bool run_out_of_space, bool terminate_early);
+  void DoTest(bool run_out_of_space,
+              bool terminate_early,
+              bool use_kernel_partition);
   void SetUp() {
   }
   void TearDown() {
@@ -83,10 +85,13 @@
 
 TEST_F(FilesystemCopierActionTest, RunAsRootSimpleTest) {
   ASSERT_EQ(0, getuid());
-  DoTest(false, false);
+  DoTest(false, false, false);
+
+  DoTest(false, false, true);
 }
 void FilesystemCopierActionTest::DoTest(bool run_out_of_space,
-                                        bool terminate_early) {
+                                        bool terminate_early,
+                                        bool use_kernel_partition) {
   GMainLoop *loop = g_main_loop_new(g_main_context_default(), FALSE);
 
   string a_loop_file;
@@ -132,7 +137,10 @@
   // Set up the action objects
   InstallPlan install_plan;
   install_plan.is_full_update = false;
-  install_plan.install_path = b_dev;
+  if (use_kernel_partition)
+    install_plan.kernel_install_path = b_dev;
+  else
+    install_plan.install_path = b_dev;
 
   ActionProcessor processor;
   FilesystemCopierActionTestDelegate delegate;
@@ -140,7 +148,7 @@
   processor.set_delegate(&delegate);
 
   ObjectFeederAction<InstallPlan> feeder_action;
-  FilesystemCopierAction copier_action;
+  FilesystemCopierAction copier_action(use_kernel_partition);
   ObjectCollectorAction<InstallPlan> collector_action;
 
   BondActions(&feeder_action, &copier_action);
@@ -202,7 +210,7 @@
 
   processor.set_delegate(&delegate);
 
-  FilesystemCopierAction copier_action;
+  FilesystemCopierAction copier_action(false);
   ObjectCollectorAction<InstallPlan> collector_action;
 
   BondActions(&copier_action, &collector_action);
@@ -222,9 +230,9 @@
   processor.set_delegate(&delegate);
 
   ObjectFeederAction<InstallPlan> feeder_action;
-  InstallPlan install_plan(true, "", "", "");
+  InstallPlan install_plan(true, "", "", "", "");
   feeder_action.set_obj(install_plan);
-  FilesystemCopierAction copier_action;
+  FilesystemCopierAction copier_action(false);
   ObjectCollectorAction<InstallPlan> collector_action;
 
   BondActions(&feeder_action, &copier_action);
@@ -246,9 +254,9 @@
   processor.set_delegate(&delegate);
 
   ObjectFeederAction<InstallPlan> feeder_action;
-  InstallPlan install_plan(false, "", "", "/some/missing/file/path");
+  InstallPlan install_plan(false, "", "", "/no/such/file", "/no/such/file");
   feeder_action.set_obj(install_plan);
-  FilesystemCopierAction copier_action;
+  FilesystemCopierAction copier_action(false);
   ObjectCollectorAction<InstallPlan> collector_action;
 
   BondActions(&copier_action, &collector_action);
@@ -264,12 +272,12 @@
 
 TEST_F(FilesystemCopierActionTest, RunAsRootNoSpaceTest) {
   ASSERT_EQ(0, getuid());
-  DoTest(true, false);
+  DoTest(true, false, false);
 }
 
 TEST_F(FilesystemCopierActionTest, RunAsRootTerminateEarlyTest) {
   ASSERT_EQ(0, getuid());
-  DoTest(false, true);
+  DoTest(false, true, false);
 }
 
 }  // namespace chromeos_update_engine
diff --git a/install_plan.h b/install_plan.h
index 9151e1b..fba89fe 100644
--- a/install_plan.h
+++ b/install_plan.h
@@ -17,23 +17,27 @@
   InstallPlan(bool is_full,
               const std::string& url,
               const std::string& hash,
-              const std::string& install_path)
+              const std::string& install_path,
+              const std::string& kernel_install_path)
       : is_full_update(is_full),
         download_url(url),
         download_hash(hash),
-        install_path(install_path) {}
+        install_path(install_path),
+        kernel_install_path(kernel_install_path) {}
   InstallPlan() : is_full_update(false) {}
 
   bool is_full_update;
   std::string download_url;  // url to download from
   std::string download_hash;  // hash of the data at the url
   std::string install_path;  // path to install device
+  std::string kernel_install_path;  // path to kernel install device
 
   bool operator==(const InstallPlan& that) const {
     return (is_full_update == that.is_full_update) &&
            (download_url == that.download_url) &&
            (download_hash == that.download_hash) &&
-           (install_path == that.install_path);
+           (install_path == that.install_path) &&
+           (kernel_install_path == that.kernel_install_path);
   }
   bool operator!=(const InstallPlan& that) const {
     return !((*this) == that);
@@ -42,7 +46,8 @@
     LOG(INFO) << "InstallPlan: "
               << (is_full_update ? "full_update" : "delta_update")
               << ", url: " << download_url << ", hash: " << download_hash
-              << ", install_path: " << install_path;
+              << ", install_path: " << install_path
+              << ", kernel_install_path: " << kernel_install_path;
   }
 };
 
diff --git a/split_file_writer.cc b/split_file_writer.cc
index e868947..690d4e3 100644
--- a/split_file_writer.cc
+++ b/split_file_writer.cc
@@ -49,8 +49,9 @@
   // to the first FileWriter.
   if (bytes_received_ < static_cast<off_t>(sizeof(uint64_t))) {
     // Write more to the initial buffer
-    size_t bytes_to_copy = min(count,
-                               sizeof(first_length_buf_) - bytes_received_);
+    size_t bytes_to_copy = min(static_cast<off_t>(count),
+                               static_cast<off_t>(sizeof(first_length_buf_)) -
+                               bytes_received_);
     memcpy(&first_length_buf_[bytes_received_], bytes, bytes_to_copy);
     bytes_received_ += bytes_to_copy;
     count -= bytes_to_copy;
diff --git a/test_utils.h b/test_utils.h
index 4bc107e..ca797d9 100644
--- a/test_utils.h
+++ b/test_utils.h
@@ -9,8 +9,10 @@
 #include <string>
 #include <vector>
 #include <gtest/gtest.h>
+#include "base/scoped_ptr.h"
 #include "update_engine/action.h"
 #include "update_engine/subprocess.h"
+#include "update_engine/utils.h"
 
 // These are some handy functions for unittests.
 
@@ -119,6 +121,20 @@
   DISALLOW_COPY_AND_ASSIGN(ScopedLoopbackDeviceReleaser);
 };
 
+class ScopedTempFile {
+ public:
+  ScopedTempFile() {
+    EXPECT_TRUE(utils::MakeTempFile("/tmp/update_engine_test_temp_file.XXXXXX",
+                                    &path_,
+                                    NULL));
+    unlinker_.reset(new ScopedPathUnlinker(path_));
+  }
+  const std::string& GetPath() { return path_; }
+ private:
+  std::string path_;
+  scoped_ptr<ScopedPathUnlinker> unlinker_;
+};
+
 // Useful actions for test
 
 class NoneType;
@@ -191,4 +207,4 @@
 
 }  // namespace chromeos_update_engine
 
-#endif  // CHROMEOS_PLATFORM_UPDATE_ENGINE_TEST_UTILS_H__
\ No newline at end of file
+#endif  // CHROMEOS_PLATFORM_UPDATE_ENGINE_TEST_UTILS_H__
diff --git a/update_attempter.cc b/update_attempter.cc
index 6b8c361..84410ad 100644
--- a/update_attempter.cc
+++ b/update_attempter.cc
@@ -35,7 +35,9 @@
   shared_ptr<OmahaResponseHandlerAction> response_handler_action(
       new OmahaResponseHandlerAction);
   shared_ptr<FilesystemCopierAction> filesystem_copier_action(
-      new FilesystemCopierAction);
+      new FilesystemCopierAction(false));
+  shared_ptr<FilesystemCopierAction> filesystem_copier_action_kernel(
+      new FilesystemCopierAction(true));
   shared_ptr<DownloadAction> download_action(
       new DownloadAction(new LibcurlHttpFetcher));
   shared_ptr<PostinstallRunnerAction> postinstall_runner_action(
@@ -49,6 +51,8 @@
   actions_.push_back(shared_ptr<AbstractAction>(update_check_action));
   actions_.push_back(shared_ptr<AbstractAction>(response_handler_action));
   actions_.push_back(shared_ptr<AbstractAction>(filesystem_copier_action));
+  actions_.push_back(shared_ptr<AbstractAction>(
+      filesystem_copier_action_kernel));
   actions_.push_back(shared_ptr<AbstractAction>(download_action));
   actions_.push_back(shared_ptr<AbstractAction>(postinstall_runner_action));
   actions_.push_back(shared_ptr<AbstractAction>(set_bootable_flag_action));
@@ -64,7 +68,10 @@
   BondActions(request_prep_action.get(), update_check_action.get());
   BondActions(update_check_action.get(), response_handler_action.get());
   BondActions(response_handler_action.get(), filesystem_copier_action.get());
-  BondActions(filesystem_copier_action.get(), download_action.get());
+  BondActions(response_handler_action.get(),
+              filesystem_copier_action_kernel.get());
+  BondActions(filesystem_copier_action_kernel.get(),
+              download_action.get());
   // TODO(adlr): Bond these actions together properly
   // BondActions(download_action.get(), install_action.get());
   // BondActions(install_action.get(), postinstall_runner_action.get());
diff --git a/utils.cc b/utils.cc
index 85d9e7d..83096d2 100644
--- a/utils.cc
+++ b/utils.cc
@@ -297,7 +297,7 @@
   return 0 == str.compare(0, prefix.size(), prefix);
 }
 
-const std::string BootDevice() {
+const string BootDevice() {
   string proc_cmdline;
   if (!ReadFileToString("/proc/cmdline", &proc_cmdline))
     return "";
@@ -324,6 +324,21 @@
   // TODO(adlr): use findfs to figure out UUID= or LABEL= filesystems
 }
 
+const string BootKernelDevice(const std::string& boot_device) {
+  // Currntly this assumes the last digit of the boot device is
+  // 3, 5, or 7, and changes it to 2, 4, or 6, respectively, to
+  // get the kernel device.
+  string ret = boot_device;
+  if (ret.empty())
+    return ret;
+  char last_char = ret[ret.size() - 1];
+  if (last_char == '3' || last_char == '5' || last_char == '7') {
+    ret[ret.size() - 1] = last_char - 1;
+    return ret;
+  }
+  return "";
+}
+
 bool MountFilesystem(const string& device,
                      const string& mountpoint,
                      unsigned long mountflags) {
diff --git a/utils.h b/utils.h
index 7983eec..e5d099f 100644
--- a/utils.h
+++ b/utils.h
@@ -135,11 +135,19 @@
   }
 }
 
-// Returns the currently booted device. "/dev/sda1", for example.
+// Returns the currently booted device. "/dev/sda3", for example.
 // This will not interpret LABEL= or UUID=. You'll need to use findfs
 // or something with equivalent funcionality to interpret those.
 const std::string BootDevice();
 
+// Returns the currently booted kernel device, "dev/sda2", for example.
+// Client must pass in the boot device. The suggested calling convention
+// is: BootKernelDevice(BootDevice()).
+// This function works by doing string modification on boot_device.
+// Returns empty string on failure.
+const std::string BootKernelDevice(const std::string& boot_device);
+
+
 }  // namespace utils
 
 // Class to unmount FS when object goes out of scope
diff --git a/utils_unittest.cc b/utils_unittest.cc
index 74f349d..5ab78cd 100644
--- a/utils_unittest.cc
+++ b/utils_unittest.cc
@@ -88,6 +88,20 @@
   EXPECT_FALSE(utils::BootDevice().empty());
 }
 
+TEST(UtilsTest, BootKernelDeviceTest) {
+  EXPECT_EQ("", utils::BootKernelDevice("foo"));
+  EXPECT_EQ("", utils::BootKernelDevice("/dev/sda0"));
+  EXPECT_EQ("", utils::BootKernelDevice("/dev/sda1"));
+  EXPECT_EQ("", utils::BootKernelDevice("/dev/sda2"));
+  EXPECT_EQ("/dev/sda2", utils::BootKernelDevice("/dev/sda3"));
+  EXPECT_EQ("", utils::BootKernelDevice("/dev/sda4"));
+  EXPECT_EQ("/dev/sda4", utils::BootKernelDevice("/dev/sda5"));
+  EXPECT_EQ("", utils::BootKernelDevice("/dev/sda6"));
+  EXPECT_EQ("/dev/sda6", utils::BootKernelDevice("/dev/sda7"));
+  EXPECT_EQ("", utils::BootKernelDevice("/dev/sda8"));
+  EXPECT_EQ("", utils::BootKernelDevice("/dev/sda9"));
+}
+
 TEST(UtilsTest, RecursiveUnlinkDirTest) {
   EXPECT_EQ(0, mkdir("RecursiveUnlinkDirTest-a", 0755));
   EXPECT_EQ(0, mkdir("RecursiveUnlinkDirTest-b", 0755));