Mark the new kernel invalid when starting an update.

Before we overwrite the new kernel, mark it as unbootable by setting the GPT
flags "successful" and "tries" to 0. This is good, but not critical as a
general behavior because it prevents the firmware from even trying a kernel
we think will be bad.

It's more useful, because it gives us a definitive way to know if the other
kernel is expected to be valid for purposes of things like rollback. There
will be a future CL to use it for preventing rollback to a known invalid
installation.

Also adds a MockHardware implementation backed by the FakeHardware
implementation, and switches MockSystemState to use it.

BUG=chromium:280816
TEST=Manual watching of flags, and multiple updates.
CQ-DEPEND=CL:176177

Change-Id: Idb083279cd1438a555c5165c69b25c351207e382
Reviewed-on: https://chromium-review.googlesource.com/176169
Reviewed-by: Don Garrett <[email protected]>
Tested-by: Don Garrett <[email protected]>
Reviewed-by: David Zeuthen <[email protected]>
Commit-Queue: Don Garrett <[email protected]>
diff --git a/SConstruct b/SConstruct
index ca6372d..fbcfe78 100644
--- a/SConstruct
+++ b/SConstruct
@@ -200,6 +200,7 @@
 env['LIBS'] = Split("""bz2
                        crypto
                        curl
+                       dm-bht
                        ext2fs
                        gflags
                        glib-2.0
diff --git a/delta_performer_unittest.cc b/delta_performer_unittest.cc
index 80aa064..d2a3ebd 100644
--- a/delta_performer_unittest.cc
+++ b/delta_performer_unittest.cc
@@ -1162,7 +1162,7 @@
   DeltaPerformer *performer = new DeltaPerformer(&prefs,
                                                  &mock_system_state,
                                                  &install_plan);
-  FakeHardware* fake_hardware = mock_system_state.get_fake_hardware();
+  FakeHardware& fake_hardware = mock_system_state.get_mock_hardware().fake();
 
   string temp_dir;
   EXPECT_TRUE(utils::MakeTempDirectory("/tmp/PublicKeyFromResponseTests.XXXXXX",
@@ -1172,46 +1172,46 @@
   EXPECT_EQ(0, System(StringPrintf("touch %s", existing_file.c_str())));
 
   // Non-official build, non-existing public-key, key in response -> true
-  fake_hardware->SetIsOfficialBuild(false);
+  fake_hardware.SetIsOfficialBuild(false);
   performer->public_key_path_ = non_existing_file;
   install_plan.public_key_rsa = "VGVzdAo="; // result of 'echo "Test" | base64'
   EXPECT_TRUE(performer->GetPublicKeyFromResponse(&key_path));
   EXPECT_FALSE(key_path.empty());
   EXPECT_EQ(unlink(key_path.value().c_str()), 0);
   // Same with official build -> false
-  fake_hardware->SetIsOfficialBuild(true);
+  fake_hardware.SetIsOfficialBuild(true);
   EXPECT_FALSE(performer->GetPublicKeyFromResponse(&key_path));
 
   // Non-official build, existing public-key, key in response -> false
-  fake_hardware->SetIsOfficialBuild(false);
+  fake_hardware.SetIsOfficialBuild(false);
   performer->public_key_path_ = existing_file;
   install_plan.public_key_rsa = "VGVzdAo="; // result of 'echo "Test" | base64'
   EXPECT_FALSE(performer->GetPublicKeyFromResponse(&key_path));
   // Same with official build -> false
-  fake_hardware->SetIsOfficialBuild(true);
+  fake_hardware.SetIsOfficialBuild(true);
   EXPECT_FALSE(performer->GetPublicKeyFromResponse(&key_path));
 
   // Non-official build, non-existing public-key, no key in response -> false
-  fake_hardware->SetIsOfficialBuild(false);
+  fake_hardware.SetIsOfficialBuild(false);
   performer->public_key_path_ = non_existing_file;
   install_plan.public_key_rsa = "";
   EXPECT_FALSE(performer->GetPublicKeyFromResponse(&key_path));
   // Same with official build -> false
-  fake_hardware->SetIsOfficialBuild(true);
+  fake_hardware.SetIsOfficialBuild(true);
   EXPECT_FALSE(performer->GetPublicKeyFromResponse(&key_path));
 
   // Non-official build, existing public-key, no key in response -> false
-  fake_hardware->SetIsOfficialBuild(false);
+  fake_hardware.SetIsOfficialBuild(false);
   performer->public_key_path_ = existing_file;
   install_plan.public_key_rsa = "";
   EXPECT_FALSE(performer->GetPublicKeyFromResponse(&key_path));
   // Same with official build -> false
-  fake_hardware->SetIsOfficialBuild(true);
+  fake_hardware.SetIsOfficialBuild(true);
   EXPECT_FALSE(performer->GetPublicKeyFromResponse(&key_path));
 
   // Non-official build, non-existing public-key, key in response
   // but invalid base64 -> false
-  fake_hardware->SetIsOfficialBuild(false);
+  fake_hardware.SetIsOfficialBuild(false);
   performer->public_key_path_ = non_existing_file;
   install_plan.public_key_rsa = "not-valid-base64";
   EXPECT_FALSE(performer->GetPublicKeyFromResponse(&key_path));
diff --git a/fake_hardware.h b/fake_hardware.h
index 4351e2c..97ab8dd 100644
--- a/fake_hardware.h
+++ b/fake_hardware.h
@@ -5,6 +5,8 @@
 #ifndef CHROMEOS_PLATFORM_UPDATE_ENGINE_FAKE_HARDWARE_H__
 #define CHROMEOS_PLATFORM_UPDATE_ENGINE_FAKE_HARDWARE_H__
 
+#include <map>
+
 #include "update_engine/hardware_interface.h"
 
 namespace chromeos_update_engine {
@@ -13,7 +15,8 @@
 class FakeHardware : public HardwareInterface {
  public:
   FakeHardware()
-    : boot_device_("/dev/sdz5"),
+    : kernel_device_("/dev/sdz4"),
+      boot_device_("/dev/sdz5"),
       is_official_build_(true),
       is_normal_boot_mode_(true),
       hardware_class_("Fake HWID BLAH-1234"),
@@ -21,7 +24,18 @@
       ec_version_("Fake EC v1.0a") {}
 
   // HardwareInterface methods.
+  virtual const std::string BootKernelDevice() { return kernel_device_; }
   virtual const std::string BootDevice() { return boot_device_; }
+  virtual bool IsKernelBootable(const std::string& kernel_device,
+                                bool* bootable)
+      { std::map<std::string, bool>::const_iterator i =
+            is_bootable_.find(kernel_device);
+        *bootable = (i != is_bootable_.end()) ? i->second : true;
+        return true; }
+
+  virtual bool MarkKernelUnbootable(const std::string& kernel_device)
+      { is_bootable_[kernel_device] = false; return true;}
+
   virtual bool IsOfficialBuild() { return is_official_build_; }
   virtual bool IsNormalBootMode() { return is_normal_boot_mode_; }
   virtual std::string GetHardwareClass() { return hardware_class_; }
@@ -54,7 +68,9 @@
   }
 
  private:
+  std::string kernel_device_;
   std::string boot_device_;
+  std::map<std::string, bool> is_bootable_;
   bool is_official_build_;
   bool is_normal_boot_mode_;
   std::string hardware_class_;
diff --git a/filesystem_copier_action.cc b/filesystem_copier_action.cc
index 9e43482..dbc3c29 100644
--- a/filesystem_copier_action.cc
+++ b/filesystem_copier_action.cc
@@ -73,11 +73,28 @@
     return;
   }
   install_plan_ = GetInputObject();
-  if (!verify_hash_ &&
-      (install_plan_.is_resume || install_plan_.is_full_update)) {
+
+  // No need to copy on a resume.
+  if (!verify_hash_ && install_plan_.is_resume) {
     // No copy or hash verification needed. Done!
-    LOG(INFO) << "filesystem copying skipped: "
-              << (install_plan_.is_resume ? "resumed" : "full") << " update";
+    LOG(INFO) << "filesystem copying skipped on resumed update.";
+    if (HasOutputPipe())
+      SetOutputObject(install_plan_);
+    abort_action_completer.set_code(kErrorCodeSuccess);
+    return;
+  }
+
+  if (copying_kernel_install_path_) {
+    if (!system_state_->hardware()->MarkKernelUnbootable(
+        install_plan_.kernel_install_path)) {
+      PLOG(ERROR) << "Unable to clear kernel GPT boot flags: " <<
+          install_plan_.kernel_install_path;
+    }
+  }
+
+  if (!verify_hash_ && install_plan_.is_full_update) {
+    // No copy or hash verification needed. Done!
+    LOG(INFO) << "filesystem copying skipped on full update.";
     if (HasOutputPipe())
       SetOutputObject(install_plan_);
     abort_action_completer.set_code(kErrorCodeSuccess);
diff --git a/filesystem_copier_action_unittest.cc b/filesystem_copier_action_unittest.cc
index 7573c91..8b6e627 100644
--- a/filesystem_copier_action_unittest.cc
+++ b/filesystem_copier_action_unittest.cc
@@ -12,6 +12,7 @@
 #include <base/string_util.h>
 #include <base/stringprintf.h>
 #include <glib.h>
+#include <gmock/gmock.h>
 #include <gtest/gtest.h>
 
 #include "update_engine/filesystem_copier_action.h"
@@ -40,8 +41,6 @@
   }
   void TearDown() {
   }
-
-  MockSystemState mock_system_state_;
 };
 
 class FilesystemCopierActionTestDelegate : public ActionProcessorDelegate {
@@ -108,11 +107,11 @@
 // issue with the chroot environiment, library versions we use, etc.
 TEST_F(FilesystemCopierActionTest, DISABLED_RunAsRootSimpleTest) {
   ASSERT_EQ(0, getuid());
-  bool test = DoTest(false, false, false, 0);
+  bool test = DoTest(false, false, true, 0);
   EXPECT_TRUE(test);
   if (!test)
     return;
-  test = DoTest(false, false, true, 0);
+  test = DoTest(false, false, false, 0);
   EXPECT_TRUE(test);
 }
 
@@ -120,6 +119,8 @@
                                         bool terminate_early,
                                         bool use_kernel_partition,
                                         int verify_hash) {
+  MockSystemState mock_system_state;
+
   GMainLoop *loop = g_main_loop_new(g_main_context_default(), FALSE);
 
   string a_loop_file;
@@ -196,10 +197,13 @@
     }
   }
 
+  EXPECT_CALL(mock_system_state.get_mock_hardware(),
+              MarkKernelUnbootable(a_dev)).Times(use_kernel_partition ? 1 : 0);
+
   ActionProcessor processor;
 
   ObjectFeederAction<InstallPlan> feeder_action;
-  FilesystemCopierAction copier_action(&mock_system_state_,
+  FilesystemCopierAction copier_action(&mock_system_state,
                                        use_kernel_partition,
                                        verify_hash != 0);
   ObjectCollectorAction<InstallPlan> collector_action;
@@ -270,6 +274,14 @@
   EXPECT_TRUE(is_install_plan_eq);
   success = success && is_install_plan_eq;
 
+  LOG(INFO) << "Verifying bootable flag on: " << a_dev;
+  bool bootable;
+  EXPECT_TRUE(mock_system_state.get_mock_hardware().fake().
+                  IsKernelBootable(a_dev, &bootable));
+  // We should always mark a partition as unbootable if it's a kernel
+  // partition, but never if it's anything else.
+  EXPECT_EQ(bootable, !use_kernel_partition);
+
   return success;
 }
 
@@ -290,11 +302,12 @@
 
 TEST_F(FilesystemCopierActionTest, MissingInputObjectTest) {
   ActionProcessor processor;
+  MockSystemState mock_system_state;
   FilesystemCopierActionTest2Delegate delegate;
 
   processor.set_delegate(&delegate);
 
-  FilesystemCopierAction copier_action(&mock_system_state_, false, false);
+  FilesystemCopierAction copier_action(&mock_system_state, false, false);
   ObjectCollectorAction<InstallPlan> collector_action;
 
   BondActions(&copier_action, &collector_action);
@@ -309,6 +322,7 @@
 
 TEST_F(FilesystemCopierActionTest, ResumeTest) {
   ActionProcessor processor;
+  MockSystemState mock_system_state;
   FilesystemCopierActionTest2Delegate delegate;
 
   processor.set_delegate(&delegate);
@@ -317,7 +331,7 @@
   const char* kUrl = "http://some/url";
   InstallPlan install_plan(false, true, kUrl, 0, "", 0, "", "", "", "");
   feeder_action.set_obj(install_plan);
-  FilesystemCopierAction copier_action(&mock_system_state_, false, false);
+  FilesystemCopierAction copier_action(&mock_system_state, false, false);
   ObjectCollectorAction<InstallPlan> collector_action;
 
   BondActions(&feeder_action, &copier_action);
@@ -335,6 +349,7 @@
 
 TEST_F(FilesystemCopierActionTest, NonExistentDriveTest) {
   ActionProcessor processor;
+  MockSystemState mock_system_state;
   FilesystemCopierActionTest2Delegate delegate;
 
   processor.set_delegate(&delegate);
@@ -351,7 +366,7 @@
                            "/no/such/file",
                            "");
   feeder_action.set_obj(install_plan);
-  FilesystemCopierAction copier_action(&mock_system_state_, false, false);
+  FilesystemCopierAction copier_action(&mock_system_state, false, false);
   ObjectCollectorAction<InstallPlan> collector_action;
 
   BondActions(&copier_action, &collector_action);
@@ -388,6 +403,7 @@
 }
 
 TEST_F(FilesystemCopierActionTest, RunAsRootDetermineFilesystemSizeTest) {
+  MockSystemState mock_system_state;
   string img;
   EXPECT_TRUE(utils::MakeTempFile("/tmp/img.XXXXXX", &img, NULL));
   ScopedPathUnlinker img_unlinker(img);
@@ -400,7 +416,7 @@
 
   for (int i = 0; i < 2; ++i) {
     bool is_kernel = i == 1;
-    FilesystemCopierAction action(&mock_system_state_, is_kernel, false);
+    FilesystemCopierAction action(&mock_system_state, is_kernel, false);
     EXPECT_EQ(kint64max, action.filesystem_size_);
     {
       int fd = HANDLE_EINTR(open(img.c_str(), O_RDONLY));
diff --git a/hardware.cc b/hardware.cc
index 6ab0f22..d547c2d 100644
--- a/hardware.cc
+++ b/hardware.cc
@@ -10,6 +10,16 @@
 #include <rootdev/rootdev.h>
 #include <vboot/crossystem.h>
 
+extern "C" {
+#include "vboot/vboot_host.h"
+}
+
+// We don't use these variables, but libcgpt needs them defined to link.
+// TODO(dgarrett) chromium:318536
+const char* progname = "";
+const char* command = "";
+void (*uuid_generator)(uint8_t* buffer) = NULL;
+
 #include "update_engine/subprocess.h"
 #include "update_engine/utils.h"
 
@@ -18,6 +28,10 @@
 
 namespace chromeos_update_engine {
 
+const string Hardware::BootKernelDevice() {
+  return utils::KernelDeviceOfBootDevice(Hardware::BootDevice());
+}
+
 const string Hardware::BootDevice() {
   char boot_path[PATH_MAX];
   // Resolve the boot device path fully, including dereferencing
@@ -35,6 +49,60 @@
   return boot_path;
 }
 
+bool Hardware::IsKernelBootable(const std::string& kernel_device,
+                                bool* bootable) {
+
+  if (kernel_device == BootKernelDevice()) {
+    LOG(ERROR) << "Refusing to mark current kernel as unbootable.";
+    return false;
+  }
+
+  CgptAddParams params;
+  memset(&params, '\0', sizeof(params));
+
+  string root_dev = utils::RootDevice(kernel_device);
+  string partition_number_str = utils::PartitionNumber(kernel_device);
+  uint32_t partition_number = atoi(partition_number_str.c_str());
+
+  params.drive_name = const_cast<char *>(root_dev.c_str());
+  params.partition = partition_number;
+
+  int retval = CgptGetPartitionDetails(&params);
+  if (retval != CGPT_OK)
+    return false;
+
+  *bootable = params.successful || (params.tries > 0);
+  return true;
+}
+
+bool Hardware::MarkKernelUnbootable(const std::string& kernel_device) {
+  LOG(INFO) << "MarkPartitionUnbootable: " << kernel_device;
+
+  string root_dev = utils::RootDevice(kernel_device);
+  string partition_number_str = utils::PartitionNumber(kernel_device);
+  uint32_t partition_number = atoi(partition_number_str.c_str());
+
+  CgptAddParams params;
+  memset(&params, 0, sizeof(params));
+
+  params.drive_name = const_cast<char *>(root_dev.c_str());
+  params.partition = partition_number;
+
+  params.successful = false;
+  params.set_successful = true;
+
+  params.tries = 0;
+  params.set_tries = true;
+
+  int retval = CgptSetAttributes(&params);
+  if (retval != CGPT_OK) {
+    LOG(ERROR) << "Marking kernel unbootable failed.";
+    return false;
+  }
+
+  return true;
+}
+
 bool Hardware::IsOfficialBuild() {
   return VbGetSystemPropertyInt("debug_build") == 0;
 }
diff --git a/hardware.h b/hardware.h
index 5ab1fca..2693e3b 100644
--- a/hardware.h
+++ b/hardware.h
@@ -17,7 +17,11 @@
   Hardware() {}
 
   // HardwareInterface methods.
+  virtual const std::string BootKernelDevice();
   virtual const std::string BootDevice();
+  virtual bool IsKernelBootable(const std::string& kernel_device,
+                                bool* bootable);
+  virtual bool MarkKernelUnbootable(const std::string& kernel_device);
   virtual bool IsOfficialBuild();
   virtual bool IsNormalBootMode();
   virtual std::string GetHardwareClass();
diff --git a/hardware_interface.h b/hardware_interface.h
index 8909058..29baec4 100644
--- a/hardware_interface.h
+++ b/hardware_interface.h
@@ -17,11 +17,22 @@
 // unit testing.
 class HardwareInterface {
  public:
-  // 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.
+  // Returns the currently booted kernel partition. "/dev/sda2", for example.
+  virtual const std::string BootKernelDevice() = 0;
+
+  // Returns the currently booted rootfs partition. "/dev/sda3", for example.
   virtual const std::string BootDevice() = 0;
 
+  // Is the specified kernel partition currently bootable, based on GPT flags?
+  // Returns success.
+  virtual bool IsKernelBootable(const std::string& kernel_device,
+                                bool* bootable) = 0;
+
+  // Mark the specified kernel partition unbootable in GPT flags. We mark
+  // the other kernel as bootable inside postinst, not inside the UE.
+  // Returns success.
+  virtual bool MarkKernelUnbootable(const std::string& kernel_device) = 0;
+
   // Returns true if this is an official Chrome OS build, false otherwise.
   virtual bool IsOfficialBuild() = 0;
 
diff --git a/mock_hardware.h b/mock_hardware.h
new file mode 100644
index 0000000..6a27488
--- /dev/null
+++ b/mock_hardware.h
@@ -0,0 +1,78 @@
+// Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROMEOS_PLATFORM_UPDATE_ENGINE_MOCK_HARDWARE_H__
+#define CHROMEOS_PLATFORM_UPDATE_ENGINE_MOCK_HARDWARE_H__
+
+#include "fake_hardware.h"
+
+#include <gmock/gmock.h>
+
+namespace chromeos_update_engine {
+
+// A mocked, fake implementation of HardwareInterface.
+class MockHardware : public HardwareInterface {
+public:
+  MockHardware() {
+    // Delegate all calls to the fake instance
+    ON_CALL(*this, BootKernelDevice())
+      .WillByDefault(testing::Invoke(&fake_,
+            &FakeHardware::BootKernelDevice));
+    ON_CALL(*this, BootDevice())
+      .WillByDefault(testing::Invoke(&fake_,
+            &FakeHardware::BootDevice));
+    ON_CALL(*this, IsKernelBootable(testing::_, testing::_))
+      .WillByDefault(testing::Invoke(&fake_,
+            &FakeHardware::IsKernelBootable));
+    ON_CALL(*this, MarkKernelUnbootable(testing::_))
+      .WillByDefault(testing::Invoke(&fake_,
+            &FakeHardware::MarkKernelUnbootable));
+    ON_CALL(*this, IsOfficialBuild())
+      .WillByDefault(testing::Invoke(&fake_,
+            &FakeHardware::IsOfficialBuild));
+    ON_CALL(*this, IsNormalBootMode())
+      .WillByDefault(testing::Invoke(&fake_,
+            &FakeHardware::IsNormalBootMode));
+    ON_CALL(*this, GetHardwareClass())
+      .WillByDefault(testing::Invoke(&fake_,
+            &FakeHardware::GetHardwareClass));
+    ON_CALL(*this, GetFirmwareVersion())
+      .WillByDefault(testing::Invoke(&fake_,
+            &FakeHardware::GetFirmwareVersion));
+    ON_CALL(*this, GetECVersion())
+      .WillByDefault(testing::Invoke(&fake_,
+            &FakeHardware::GetECVersion));
+  }
+
+  virtual ~MockHardware() {}
+
+  // Hardware overrides.
+  MOCK_METHOD0(BootKernelDevice, const std::string());
+  MOCK_METHOD0(BootDevice, const std::string());
+  MOCK_METHOD2(IsKernelBootable,
+               bool(const std::string& kernel_device, bool* bootable));
+  MOCK_METHOD1(MarkKernelUnbootable,
+               bool(const std::string& kernel_device));
+  MOCK_METHOD0(IsOfficialBuild, bool());
+  MOCK_METHOD0(IsNormalBootMode, bool());
+  MOCK_METHOD0(GetHardwareClass, std::string());
+  MOCK_METHOD0(GetFirmwareVersion, std::string());
+  MOCK_METHOD0(GetECVersion, std::string());
+
+  // Returns a reference to the underlying FakeHardware.
+  FakeHardware& fake() {
+    return fake_;
+  }
+
+private:
+  // The underlying FakeHardware.
+  FakeHardware fake_;
+
+  DISALLOW_COPY_AND_ASSIGN(MockHardware);
+};
+
+
+}  // namespace chromeos_update_engine
+
+#endif  // CHROMEOS_PLATFORM_UPDATE_ENGINE_MOCK_HARDWARE_H__
diff --git a/mock_system_state.h b/mock_system_state.h
index 193438b..c3fb32b 100644
--- a/mock_system_state.h
+++ b/mock_system_state.h
@@ -15,7 +15,7 @@
 #include "update_engine/mock_p2p_manager.h"
 #include "update_engine/mock_payload_state.h"
 #include "update_engine/clock.h"
-#include "update_engine/fake_hardware.h"
+#include "update_engine/mock_hardware.h"
 #include "update_engine/prefs_mock.h"
 #include "update_engine/system_state.h"
 
@@ -96,8 +96,8 @@
     hardware_ = hardware;
   }
 
-  inline FakeHardware* get_fake_hardware() {
-    return &default_hardware_;
+  inline MockHardware& get_mock_hardware() {
+    return default_hardware_;
   }
 
   inline void set_prefs(PrefsInterface* prefs) {
@@ -145,7 +145,7 @@
 
   // These are the other object we own.
   Clock default_clock_;
-  FakeHardware default_hardware_;
+  MockHardware default_hardware_;
   OmahaRequestParams default_request_params_;
 
   // These are pointers to objects which caller can override.
diff --git a/payload_state_unittest.cc b/payload_state_unittest.cc
index eb231cb..6616684 100644
--- a/payload_state_unittest.cc
+++ b/payload_state_unittest.cc
@@ -1360,8 +1360,8 @@
   prefs.Init(FilePath(temp_dir));
   mock_system_state.set_prefs(&prefs);
 
-  FakeHardware* fake_hardware = mock_system_state.get_fake_hardware();
-  fake_hardware->SetBootDevice("/dev/sda3");
+  FakeHardware& fake_hardware = mock_system_state.get_mock_hardware().fake();
+  fake_hardware.SetBootDevice("/dev/sda3");
 
   EXPECT_TRUE(payload_state.Initialize(&mock_system_state));
   SetupPayloadStateWith2Urls("Hash3141", true, &payload_state, &response);
@@ -1410,8 +1410,8 @@
   prefs.Init(FilePath(temp_dir));
   mock_system_state.set_prefs(&prefs);
 
-  FakeHardware* fake_hardware = mock_system_state.get_fake_hardware();
-  fake_hardware->SetBootDevice("/dev/sda3");
+  FakeHardware& fake_hardware = mock_system_state.get_mock_hardware().fake();
+  fake_hardware.SetBootDevice("/dev/sda3");
 
   EXPECT_TRUE(payload_state.Initialize(&mock_system_state));
   SetupPayloadStateWith2Urls("Hash3141", true, &payload_state, &response);
@@ -1422,7 +1422,7 @@
   payload_state.ExpectRebootInNewVersion("Version:12345678");
 
   // Change the BootDevice to a different one, no metric should be sent.
-  fake_hardware->SetBootDevice("/dev/sda5");
+  fake_hardware.SetBootDevice("/dev/sda5");
 
   EXPECT_CALL(*mock_system_state.mock_metrics_lib(), SendToUMA(
       "Installer.RebootToNewPartitionAttempt", _, _, _, _))
@@ -1431,7 +1431,7 @@
 
   // A second reboot in eiher partition should not send a metric.
   payload_state.ReportFailedBootIfNeeded();
-  fake_hardware->SetBootDevice("/dev/sda3");
+  fake_hardware.SetBootDevice("/dev/sda3");
   payload_state.ReportFailedBootIfNeeded();
 
   EXPECT_TRUE(utils::RecursiveUnlinkDir(temp_dir));
diff --git a/update_attempter.cc b/update_attempter.cc
index 0dfa7c4..ea307f5 100644
--- a/update_attempter.cc
+++ b/update_attempter.cc
@@ -613,10 +613,12 @@
                              false));
   shared_ptr<OmahaResponseHandlerAction> response_handler_action(
       new OmahaResponseHandlerAction(system_state_));
-  shared_ptr<FilesystemCopierAction> filesystem_copier_action(
-      new FilesystemCopierAction(system_state_, false, false));
+  // We start with the kernel so it's marked as invalid more quickly.
   shared_ptr<FilesystemCopierAction> kernel_filesystem_copier_action(
       new FilesystemCopierAction(system_state_, true, false));
+  shared_ptr<FilesystemCopierAction> filesystem_copier_action(
+      new FilesystemCopierAction(system_state_, false, false));
+
   shared_ptr<OmahaRequestAction> download_started_action(
       new OmahaRequestAction(system_state_,
                              new OmahaEvent(