update_engine: New BootControlInterface class.

The new BootControlInterface class is a platform-independent
abstraction to control the bootloader. It provides methods for setting
what partition slots are available for booting and getting the
bootloader status about the available slots.

The Chrome OS specific implementation of the bootloader was moved to
the BootControlChromeOS which now depends on the vboot_host
implementation used in Chrome OS. Follow up CL will implement the
equivalent class for Brillo.

BUG=b:23010637
TEST=unittests; cros flash from the new image and rolled back from it.

Change-Id: I0a03aeeb8c21d8c99e1866b625e6e8c96628215b
diff --git a/boot_control_chromeos.cc b/boot_control_chromeos.cc
new file mode 100644
index 0000000..ad82401
--- /dev/null
+++ b/boot_control_chromeos.cc
@@ -0,0 +1,229 @@
+//
+// Copyright (C) 2015 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#include "update_engine/boot_control_chromeos.h"
+
+#include <string>
+
+#include <base/files/file_path.h>
+#include <base/files/file_util.h>
+#include <base/strings/string_util.h>
+#include <rootdev/rootdev.h>
+
+extern "C" {
+#include <vboot/vboot_host.h>
+}
+
+#include "update_engine/utils.h"
+
+using std::string;
+
+namespace {
+
+const char* kChromeOSPartitionNameKernel = "kernel";
+const char* kChromeOSPartitionNameRoot = "root";
+const char* kAndroidPartitionNameKernel = "boot";
+const char* kAndroidPartitionNameRoot = "system";
+
+// Returns the currently booted rootfs partition. "/dev/sda3", for example.
+string GetBootDevice() {
+  char boot_path[PATH_MAX];
+  // Resolve the boot device path fully, including dereferencing through
+  // dm-verity.
+  int ret = rootdev(boot_path, sizeof(boot_path), true, false);
+  if (ret < 0) {
+    LOG(ERROR) << "rootdev failed to find the root device";
+    return "";
+  }
+  LOG_IF(WARNING, ret > 0) << "rootdev found a device name with no device node";
+
+  // This local variable is used to construct the return string and is not
+  // passed around after use.
+  return boot_path;
+}
+
+}  // namespace
+
+namespace chromeos_update_engine {
+
+bool BootControlChromeOS::Init() {
+  string boot_device = GetBootDevice();
+  if (boot_device.empty())
+    return false;
+
+  int partition_num;
+  if (!utils::SplitPartitionName(boot_device, &boot_disk_name_, &partition_num))
+    return false;
+
+  // All installed Chrome OS devices have two slots. We don't update removable
+  // devices, so we will pretend we have only one slot in that case.
+  if (IsRemovableDevice(boot_disk_name_)) {
+    LOG(INFO)
+        << "Booted from a removable device, pretending we have only one slot.";
+    num_slots_ = 1;
+  } else {
+    // TODO(deymo): Look at the actual number of slots reported in the GPT.
+    num_slots_ = 2;
+  }
+
+  // Search through the slots to see which slot has the partition_num we booted
+  // from. This should map to one of the existing slots, otherwise something is
+  // very wrong.
+  current_slot_ = 0;
+  while (current_slot_ < num_slots_ &&
+         partition_num !=
+             GetPartitionNumber(kChromeOSPartitionNameRoot, current_slot_)) {
+    current_slot_++;
+  }
+  if (current_slot_ >= num_slots_) {
+    LOG(ERROR) << "Couldn't find the slot number corresponding to the "
+                  "partition " << boot_device
+               << ", number of slots: " << num_slots_
+               << ". This device is not updateable.";
+    num_slots_ = 1;
+    current_slot_ = BootControlInterface::kInvalidSlot;
+    return false;
+  }
+
+  LOG(INFO) << "Booted from slot " << current_slot_ << " (slot "
+            << BootControlInterface::SlotName(current_slot_) << ") of "
+            << num_slots_ << " slots present on disk " << boot_disk_name_;
+  return true;
+}
+
+unsigned int BootControlChromeOS::GetNumSlots() const {
+  return num_slots_;
+}
+
+BootControlInterface::Slot BootControlChromeOS::GetCurrentSlot() const {
+  return current_slot_;
+}
+
+bool BootControlChromeOS::GetPartitionDevice(const string& partition_name,
+                                             unsigned int slot,
+                                             string* device) const {
+  int partition_num = GetPartitionNumber(partition_name, slot);
+  if (partition_num < 0)
+    return false;
+
+  string part_device = utils::MakePartitionName(boot_disk_name_, partition_num);
+  if (part_device.empty())
+    return false;
+
+  *device = part_device;
+  return true;
+}
+
+bool BootControlChromeOS::IsSlotBootable(Slot slot) const {
+  int partition_num = GetPartitionNumber(kChromeOSPartitionNameKernel, slot);
+  if (partition_num < 0)
+    return false;
+
+  CgptAddParams params;
+  memset(&params, '\0', sizeof(params));
+  params.drive_name = const_cast<char*>(boot_disk_name_.c_str());
+  params.partition = partition_num;
+
+  int retval = CgptGetPartitionDetails(&params);
+  if (retval != CGPT_OK)
+    return false;
+
+  return params.successful || params.tries > 0;
+}
+
+bool BootControlChromeOS::MarkSlotUnbootable(Slot slot) {
+  LOG(INFO) << "Marking slot " << BootControlInterface::SlotName(slot)
+            << " unbootable";
+
+  if (slot == current_slot_) {
+    LOG(ERROR) << "Refusing to mark current slot as unbootable.";
+    return false;
+  }
+
+  int partition_num = GetPartitionNumber(kChromeOSPartitionNameKernel, slot);
+  if (partition_num < 0)
+    return false;
+
+  CgptAddParams params;
+  memset(&params, 0, sizeof(params));
+
+  params.drive_name = const_cast<char*>(boot_disk_name_.c_str());
+  params.partition = partition_num;
+
+  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;
+}
+
+// static
+string BootControlChromeOS::SysfsBlockDevice(const string& device) {
+  base::FilePath device_path(device);
+  if (device_path.DirName().value() != "/dev") {
+    return "";
+  }
+  return base::FilePath("/sys/block").Append(device_path.BaseName()).value();
+}
+
+// static
+bool BootControlChromeOS::IsRemovableDevice(const string& device) {
+  string sysfs_block = SysfsBlockDevice(device);
+  string removable;
+  if (sysfs_block.empty() ||
+      !base::ReadFileToString(base::FilePath(sysfs_block).Append("removable"),
+                              &removable)) {
+    return false;
+  }
+  base::TrimWhitespaceASCII(removable, base::TRIM_ALL, &removable);
+  return removable == "1";
+}
+
+int BootControlChromeOS::GetPartitionNumber(
+    const string partition_name,
+    BootControlInterface::Slot slot) const {
+  if (slot >= num_slots_) {
+    LOG(ERROR) << "Invalid slot number: " << slot << ", we only have "
+               << num_slots_ << " slot(s)";
+    return -1;
+  }
+
+  // In Chrome OS, the partition numbers are hard-coded:
+  //   KERNEL-A=2, ROOT-A=3, KERNEL-B=4, ROOT-B=4, ...
+  // To help compatibility between different we accept both lowercase and
+  // uppercase names in the ChromeOS or Brillo standard names.
+  // See http://www.chromium.org/chromium-os/chromiumos-design-docs/disk-format
+  string partition_lower = base::StringToLowerASCII(partition_name);
+  int base_part_num = 2 + 2 * slot;
+  if (partition_lower == kChromeOSPartitionNameKernel ||
+      partition_lower == kAndroidPartitionNameKernel)
+    return base_part_num + 0;
+  if (partition_lower == kChromeOSPartitionNameRoot ||
+      partition_lower == kAndroidPartitionNameRoot)
+    return base_part_num + 1;
+  LOG(ERROR) << "Unknown Chrome OS partition name \"" << partition_name << "\"";
+  return -1;
+}
+
+}  // namespace chromeos_update_engine
diff --git a/boot_control_chromeos.h b/boot_control_chromeos.h
new file mode 100644
index 0000000..a7c269e
--- /dev/null
+++ b/boot_control_chromeos.h
@@ -0,0 +1,82 @@
+//
+// Copyright (C) 2015 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#ifndef UPDATE_ENGINE_BOOT_CONTROL_CHROMEOS_H_
+#define UPDATE_ENGINE_BOOT_CONTROL_CHROMEOS_H_
+
+#include <string>
+
+#include <gtest/gtest_prod.h>  // for FRIEND_TEST
+
+#include "update_engine/boot_control_interface.h"
+
+namespace chromeos_update_engine {
+
+// The Chrome OS implementation of the BootControlInterface. This interface
+// assumes the partition names and numbers used in Chrome OS devices.
+class BootControlChromeOS : public BootControlInterface {
+ public:
+  BootControlChromeOS() = default;
+  ~BootControlChromeOS() = default;
+
+  // Initialize the BootControl instance loading the constant values. Returns
+  // whether the operation succeeded. In case of failure, normally meaning
+  // some critical failure such as we couldn't determine the slot that we
+  // booted from, the implementation will pretend that there's only one slot and
+  // therefore A/B updates are disabled.
+  bool Init();
+
+  // BootControlInterface overrides.
+  unsigned int GetNumSlots() const override;
+  BootControlInterface::Slot GetCurrentSlot() const override;
+  bool GetPartitionDevice(const std::string& partition_name,
+                          BootControlInterface::Slot slot,
+                          std::string* device) const override;
+  bool IsSlotBootable(BootControlInterface::Slot slot) const override;
+  bool MarkSlotUnbootable(BootControlInterface::Slot slot) override;
+
+ private:
+  friend class BootControlChromeOSTest;
+  FRIEND_TEST(BootControlChromeOSTest, SysfsBlockDeviceTest);
+  FRIEND_TEST(BootControlChromeOSTest, GetPartitionNumberTest);
+
+  // Returns the sysfs block device for a root block device. For example,
+  // SysfsBlockDevice("/dev/sda") returns "/sys/block/sda". Returns an empty
+  // string if the input device is not of the "/dev/xyz" form.
+  static std::string SysfsBlockDevice(const std::string& device);
+
+  // Returns true if the root |device| (e.g., "/dev/sdb") is known to be
+  // removable, false otherwise.
+  static bool IsRemovableDevice(const std::string& device);
+
+  // Return the hard-coded partition number used in Chrome OS for the passed
+  // |partition_name| and |slot|. In case of invalid data, returns -1.
+  int GetPartitionNumber(const std::string partition_name,
+                         BootControlInterface::Slot slot) const;
+
+  // Cached values for GetNumSlots() and GetCurrentSlot().
+  BootControlInterface::Slot num_slots_{1};
+  BootControlInterface::Slot current_slot_{BootControlInterface::kInvalidSlot};
+
+  // The block device of the disk we booted from, without the partition number.
+  std::string boot_disk_name_;
+
+  DISALLOW_COPY_AND_ASSIGN(BootControlChromeOS);
+};
+
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_BOOT_CONTROL_CHROMEOS_H_
diff --git a/boot_control_chromeos_unittest.cc b/boot_control_chromeos_unittest.cc
new file mode 100644
index 0000000..6a60009
--- /dev/null
+++ b/boot_control_chromeos_unittest.cc
@@ -0,0 +1,70 @@
+//
+// Copyright (C) 2015 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#include "update_engine/boot_control_chromeos.h"
+
+#include <gtest/gtest.h>
+
+namespace chromeos_update_engine {
+
+class BootControlChromeOSTest : public ::testing::Test {
+ protected:
+  void SetUp() override {
+    // We don't run Init() for bootctl_, we set its internal values instead.
+    bootctl_.num_slots_ = 2;
+    bootctl_.current_slot_ = 0;
+    bootctl_.boot_disk_name_ = "/dev/null";
+  }
+
+  BootControlChromeOS bootctl_;  // BootControlChromeOS under test.
+};
+
+TEST_F(BootControlChromeOSTest, SysfsBlockDeviceTest) {
+  EXPECT_EQ("/sys/block/sda", bootctl_.SysfsBlockDevice("/dev/sda"));
+  EXPECT_EQ("", bootctl_.SysfsBlockDevice("/foo/sda"));
+  EXPECT_EQ("", bootctl_.SysfsBlockDevice("/dev/foo/bar"));
+  EXPECT_EQ("", bootctl_.SysfsBlockDevice("/"));
+  EXPECT_EQ("", bootctl_.SysfsBlockDevice("./"));
+  EXPECT_EQ("", bootctl_.SysfsBlockDevice(""));
+}
+
+TEST_F(BootControlChromeOSTest, GetPartitionNumberTest) {
+  // The partition name should not be case-sensitive.
+  EXPECT_EQ(2, bootctl_.GetPartitionNumber("kernel", 0));
+  EXPECT_EQ(2, bootctl_.GetPartitionNumber("boot", 0));
+  EXPECT_EQ(2, bootctl_.GetPartitionNumber("KERNEL", 0));
+  EXPECT_EQ(2, bootctl_.GetPartitionNumber("BOOT", 0));
+
+  EXPECT_EQ(3, bootctl_.GetPartitionNumber("ROOT", 0));
+  EXPECT_EQ(3, bootctl_.GetPartitionNumber("system", 0));
+
+  EXPECT_EQ(3, bootctl_.GetPartitionNumber("ROOT", 0));
+  EXPECT_EQ(3, bootctl_.GetPartitionNumber("system", 0));
+
+  // Slot B.
+  EXPECT_EQ(4, bootctl_.GetPartitionNumber("KERNEL", 1));
+  EXPECT_EQ(5, bootctl_.GetPartitionNumber("ROOT", 1));
+
+  // Slot C doesn't exists.
+  EXPECT_EQ(-1, bootctl_.GetPartitionNumber("KERNEL", 2));
+  EXPECT_EQ(-1, bootctl_.GetPartitionNumber("ROOT", 2));
+
+  // Non A/B partitions are ignored.
+  EXPECT_EQ(-1, bootctl_.GetPartitionNumber("OEM", 0));
+  EXPECT_EQ(-1, bootctl_.GetPartitionNumber("A little panda", 0));
+}
+
+}  // namespace chromeos_update_engine
diff --git a/boot_control_interface.h b/boot_control_interface.h
new file mode 100644
index 0000000..135d2eb
--- /dev/null
+++ b/boot_control_interface.h
@@ -0,0 +1,85 @@
+//
+// Copyright (C) 2015 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#ifndef UPDATE_ENGINE_BOOT_CONTROL_INTERFACE_H_
+#define UPDATE_ENGINE_BOOT_CONTROL_INTERFACE_H_
+
+#include <climits>
+#include <string>
+
+#include <base/macros.h>
+
+namespace chromeos_update_engine {
+
+// The abstract boot control interface defines the interaction with the
+// platform's bootloader hiding vendor-specific details from the rest of
+// update_engine. This interface is used for controlling where the device should
+// boot from.
+class BootControlInterface {
+ public:
+  using Slot = unsigned int;
+
+  static const Slot kInvalidSlot = UINT_MAX;
+
+  virtual ~BootControlInterface() = default;
+
+  // Return the number of update slots in the system. A system will normally
+  // have two slots, named "A" and "B" in the documentation, but sometimes
+  // images running from other media can have only one slot, like some USB
+  // image. Systems with only one slot won't be able to update.
+  virtual unsigned int GetNumSlots() const = 0;
+
+  // Return the slot where we are running the system from. On success, the
+  // result is a number between 0 and GetNumSlots() - 1. Otherwise, log an error
+  // and return kInvalidSlot.
+  virtual Slot GetCurrentSlot() const = 0;
+
+  // Determines the block device for the given partition name and slot number.
+  // The |slot| number must be between 0 and GetNumSlots() - 1 and the
+  // |partition_name| is a platform-specific name that identifies a partition on
+  // every slot. On success, returns true and stores the block device in
+  // |device|.
+  virtual bool GetPartitionDevice(const std::string& partition_name,
+                                  Slot slot,
+                                  std::string* device) const = 0;
+
+  // Returns whether the passed |slot| is marked as bootable. Returns false if
+  // the slot is invalid.
+  virtual bool IsSlotBootable(Slot slot) const = 0;
+
+  // Mark the specified slot unbootable. No other slot flags are modified.
+  // Returns true on success.
+  virtual bool MarkSlotUnbootable(Slot slot) = 0;
+
+  // Return a human-readable slot name used for logging.
+  static std::string SlotName(Slot slot) {
+    if (slot == kInvalidSlot)
+      return "INVALID";
+    if (slot < 26)
+      return std::string(1, 'A' + slot);
+    return "TOO_BIG";
+  }
+
+ protected:
+  BootControlInterface() = default;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(BootControlInterface);
+};
+
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_BOOT_CONTROL_INTERFACE_H_
diff --git a/dbus_service.cc b/dbus_service.cc
index db9a6eb..7bf8a69 100644
--- a/dbus_service.cc
+++ b/dbus_service.cc
@@ -312,13 +312,26 @@
 bool UpdateEngineService::GetRollbackPartition(
     ErrorPtr* /* error */,
     string* out_rollback_partition_name) {
-  string name = system_state_->update_attempter()->GetRollbackPartition();
+  BootControlInterface::Slot rollback_slot =
+      system_state_->update_attempter()->GetRollbackSlot();
+
+  if (rollback_slot == BootControlInterface::kInvalidSlot) {
+    out_rollback_partition_name->clear();
+    return true;
+  }
+
+  string name;
+  if (!system_state_->boot_control()->GetPartitionDevice(
+          "KERNEL", rollback_slot, &name)) {
+    LOG(ERROR) << "Invalid rollback device";
+    return false;
+  }
+
   LOG(INFO) << "Getting rollback partition name. Result: " << name;
   *out_rollback_partition_name = name;
   return true;
 }
 
-
 UpdateEngineAdaptor::UpdateEngineAdaptor(SystemState* system_state,
                                          const scoped_refptr<dbus::Bus>& bus)
     : org::chromium::UpdateEngineInterfaceAdaptor(&dbus_service_),
diff --git a/fake_boot_control.h b/fake_boot_control.h
new file mode 100644
index 0000000..508f578
--- /dev/null
+++ b/fake_boot_control.h
@@ -0,0 +1,103 @@
+//
+// Copyright (C) 2015 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#ifndef UPDATE_ENGINE_FAKE_BOOT_CONTROL_H_
+#define UPDATE_ENGINE_FAKE_BOOT_CONTROL_H_
+
+#include <map>
+#include <string>
+#include <vector>
+
+#include <base/time/time.h>
+
+#include "update_engine/boot_control_interface.h"
+
+namespace chromeos_update_engine {
+
+// Implements a fake bootloader control interface used for testing.
+class FakeBootControl : public BootControlInterface {
+ public:
+  FakeBootControl() {
+    SetNumSlots(num_slots_);
+    // The current slot should be bootable.
+    is_bootable_[current_slot_] = true;
+  }
+
+  // BootControlInterface overrides.
+  unsigned int GetNumSlots() const override { return num_slots_; }
+  BootControlInterface::Slot GetCurrentSlot() const override {
+    return current_slot_;
+  }
+
+  bool GetPartitionDevice(const std::string& partition_name,
+                          BootControlInterface::Slot slot,
+                          std::string* device) const override {
+    if (slot >= num_slots_)
+      return false;
+    auto part_it = devices_[slot].find(partition_name);
+    if (part_it == devices_[slot].end())
+      return false;
+    *device = part_it->second;
+    return true;
+  }
+
+  bool IsSlotBootable(BootControlInterface::Slot slot) const override {
+    return slot < num_slots_ && is_bootable_[slot];
+  }
+
+  bool MarkSlotUnbootable(BootControlInterface::Slot slot) override {
+    if (slot >= num_slots_)
+      return false;
+    is_bootable_[slot] = false;
+    return true;
+  }
+
+  // Setters
+  void SetNumSlots(unsigned int num_slots) {
+    num_slots_ = num_slots;
+    is_bootable_.resize(num_slots_, false);
+    devices_.resize(num_slots_);
+  }
+
+  void SetCurrentSlot(BootControlInterface::Slot slot) {
+    current_slot_ = slot;
+  }
+
+  void SetPartitionDevice(const std::string partition_name,
+                          BootControlInterface::Slot slot,
+                          const std::string device) {
+    DCHECK(slot < num_slots_);
+    devices_[slot][partition_name] = device;
+  }
+
+  void SetSlotBootable(BootControlInterface::Slot slot, bool bootable) {
+    DCHECK(slot < num_slots_);
+    is_bootable_[slot] = bootable;
+  }
+
+ private:
+  BootControlInterface::Slot num_slots_{2};
+  BootControlInterface::Slot current_slot_{0};
+
+  std::vector<bool> is_bootable_;
+  std::vector<std::map<std::string, std::string>> devices_;
+
+  DISALLOW_COPY_AND_ASSIGN(FakeBootControl);
+};
+
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_FAKE_BOOT_CONTROL_H_
diff --git a/fake_hardware.h b/fake_hardware.h
index 39cbf42..5d3da1a 100644
--- a/fake_hardware.h
+++ b/fake_hardware.h
@@ -36,43 +36,15 @@
   static const int kPowerwashCountNotSet = -1;
 
   FakeHardware()
-    : kernel_device_("/dev/sdz4"),
-      boot_device_("/dev/sdz5"),
-      is_boot_device_removable_(false),
-      kernel_devices_({"/dev/sdz2", "/dev/sdz4"}),
-      is_official_build_(true),
-      is_normal_boot_mode_(true),
-      is_oobe_complete_(false),
-      hardware_class_("Fake HWID BLAH-1234"),
-      firmware_version_("Fake Firmware v1.0.1"),
-      ec_version_("Fake EC v1.0a"),
-      powerwash_count_(kPowerwashCountNotSet) {}
+      : is_official_build_(true),
+        is_normal_boot_mode_(true),
+        is_oobe_complete_(false),
+        hardware_class_("Fake HWID BLAH-1234"),
+        firmware_version_("Fake Firmware v1.0.1"),
+        ec_version_("Fake EC v1.0a"),
+        powerwash_count_(kPowerwashCountNotSet) {}
 
   // HardwareInterface methods.
-  std::string BootKernelDevice() const override { return kernel_device_; }
-
-  std::string BootDevice() const override { return boot_device_; }
-
-  bool IsBootDeviceRemovable() const override {
-    return is_boot_device_removable_;
-  }
-
-  std::vector<std::string> GetKernelDevices() const override {
-    return kernel_devices_;
-  }
-
-  bool IsKernelBootable(const std::string& kernel_device,
-                        bool* bootable) const override {
-    auto i = is_bootable_.find(kernel_device);
-    *bootable = (i != is_bootable_.end()) ? i->second : true;
-    return true;
-  }
-
-  bool MarkKernelUnbootable(const std::string& kernel_device) override {
-    is_bootable_[kernel_device] = false;
-    return true;
-  }
-
   bool IsOfficialBuild() const override { return is_official_build_; }
 
   bool IsNormalBootMode() const override { return is_normal_boot_mode_; }
@@ -92,14 +64,6 @@
   int GetPowerwashCount() const override { return powerwash_count_; }
 
   // Setters
-  void SetBootDevice(const std::string& boot_device) {
-    boot_device_ = boot_device;
-  }
-
-  void SetIsBootDeviceRemovable(bool is_boot_device_removable) {
-    is_boot_device_removable_ = is_boot_device_removable;
-  }
-
   void SetIsOfficialBuild(bool is_official_build) {
     is_official_build_ = is_official_build;
   }
@@ -136,11 +100,6 @@
   }
 
  private:
-  std::string kernel_device_;
-  std::string boot_device_;
-  bool is_boot_device_removable_;
-  std::vector<std::string>  kernel_devices_;
-  std::map<std::string, bool> is_bootable_;
   bool is_official_build_;
   bool is_normal_boot_mode_;
   bool is_oobe_complete_;
diff --git a/fake_system_state.h b/fake_system_state.h
index 59d25b6..30ba7e9 100644
--- a/fake_system_state.h
+++ b/fake_system_state.h
@@ -24,6 +24,7 @@
 #include "metrics/metrics_library_mock.h"
 #include "power_manager/dbus-proxies.h"
 #include "power_manager/dbus-proxy-mocks.h"
+#include "update_engine/fake_boot_control.h"
 #include "update_engine/fake_clock.h"
 #include "update_engine/fake_hardware.h"
 #include "update_engine/mock_connection_manager.h"
@@ -47,6 +48,8 @@
   // various members, either the default (fake/mock) or the one set to override
   // it by client code.
 
+  BootControlInterface* boot_control() override { return boot_control_; }
+
   inline ClockInterface* clock() override { return clock_; }
 
   inline void set_device_policy(
@@ -103,6 +106,10 @@
   // implementations. For convenience, setting to a null pointer will restore
   // the default implementation.
 
+  void set_boot_control(BootControlInterface* boot_control) {
+    boot_control_ = boot_control ? boot_control : &fake_boot_control_;
+  }
+
   inline void set_clock(ClockInterface* clock) {
     clock_ = clock ? clock : &fake_clock_;
   }
@@ -162,6 +169,11 @@
   // whenever the requested default was overridden by a different
   // implementation.
 
+  inline FakeBootControl* fake_boot_control() {
+    CHECK(boot_control_ == &fake_boot_control_);
+    return &fake_boot_control_;
+  }
+
   inline FakeClock* fake_clock() {
     CHECK(clock_ == &fake_clock_);
     return &fake_clock_;
@@ -219,6 +231,7 @@
 
  private:
   // Default mock/fake implementations (owned).
+  FakeBootControl fake_boot_control_;
   FakeClock fake_clock_;
   testing::NiceMock<MockConnectionManager> mock_connection_manager_;
   FakeHardware fake_hardware_;
@@ -234,6 +247,7 @@
 
   // Pointers to objects that client code can override. They are initialized to
   // the default implementations above.
+  BootControlInterface* boot_control_{&fake_boot_control_};
   ClockInterface* clock_;
   ConnectionManagerInterface* connection_manager_;
   HardwareInterface* hardware_;
diff --git a/filesystem_verifier_action.cc b/filesystem_verifier_action.cc
index bfe3da7..df54f80 100644
--- a/filesystem_verifier_action.cc
+++ b/filesystem_verifier_action.cc
@@ -28,7 +28,7 @@
 #include <base/bind.h>
 #include <chromeos/streams/file_stream.h>
 
-#include "update_engine/hardware_interface.h"
+#include "update_engine/boot_control_interface.h"
 #include "update_engine/system_state.h"
 #include "update_engine/utils.h"
 
@@ -40,6 +40,21 @@
 const off_t kReadFileBufferSize = 128 * 1024;
 }  // namespace
 
+string PartitionTypeToString(const PartitionType partition_type) {
+  // TODO(deymo): The PartitionType class should be replaced with just the
+  // string name that comes from the payload. This function should be deleted
+  // then.
+  switch (partition_type) {
+    case PartitionType::kRootfs:
+    case PartitionType::kSourceRootfs:
+      return kLegacyPartitionNameRoot;
+    case PartitionType::kKernel:
+    case PartitionType::kSourceKernel:
+      return kLegacyPartitionNameKernel;
+  }
+  return "<unknown>";
+}
+
 FilesystemVerifierAction::FilesystemVerifierAction(
     SystemState* system_state,
     PartitionType partition_type)
@@ -57,10 +72,11 @@
   }
   install_plan_ = GetInputObject();
 
+  // TODO(deymo): Remove this from the FileSystemVerifierAction.
   if (partition_type_ == PartitionType::kKernel) {
     LOG(INFO) << "verifying kernel, marking as unbootable";
-    if (!system_state_->hardware()->MarkKernelUnbootable(
-        install_plan_.kernel_install_path)) {
+    if (!system_state_->boot_control()->MarkSlotUnbootable(
+            install_plan_.target_slot)) {
       PLOG(ERROR) << "Unable to clear kernel GPT boot flags: " <<
           install_plan_.kernel_install_path;
     }
@@ -78,33 +94,35 @@
   }
 
   string target_path;
+  string partition_name = PartitionTypeToString(partition_type_);
   switch (partition_type_) {
     case PartitionType::kRootfs:
       target_path = install_plan_.install_path;
       if (target_path.empty()) {
-        utils::GetInstallDev(system_state_->hardware()->BootDevice(),
-                             &target_path);
+        system_state_->boot_control()->GetPartitionDevice(
+            partition_name, install_plan_.target_slot, &target_path);
       }
       break;
     case PartitionType::kKernel:
       target_path = install_plan_.kernel_install_path;
       if (target_path.empty()) {
-        string rootfs_path;
-        utils::GetInstallDev(system_state_->hardware()->BootDevice(),
-                             &rootfs_path);
-        target_path = utils::KernelDeviceOfBootDevice(rootfs_path);
+        system_state_->boot_control()->GetPartitionDevice(
+            partition_name, install_plan_.target_slot, &target_path);
       }
       break;
     case PartitionType::kSourceRootfs:
-      target_path = install_plan_.source_path.empty() ?
-                    system_state_->hardware()->BootDevice() :
-                    install_plan_.source_path;
+      target_path = install_plan_.source_path;
+      if (target_path.empty()) {
+        system_state_->boot_control()->GetPartitionDevice(
+            partition_name, install_plan_.source_slot, &target_path);
+      }
       break;
     case PartitionType::kSourceKernel:
-      target_path = install_plan_.kernel_source_path.empty() ?
-                    utils::KernelDeviceOfBootDevice(
-                        system_state_->hardware()->BootDevice()) :
-                    install_plan_.kernel_source_path;
+      target_path = install_plan_.kernel_source_path;
+      if (target_path.empty()) {
+        system_state_->boot_control()->GetPartitionDevice(
+            partition_name, install_plan_.source_slot, &target_path);
+      }
       break;
   }
 
@@ -239,8 +257,7 @@
   return true;
 }
 
-void FilesystemVerifierAction::DetermineFilesystemSize(
-    const std::string& path) {
+void FilesystemVerifierAction::DetermineFilesystemSize(const string& path) {
   switch (partition_type_) {
     case PartitionType::kRootfs:
       remaining_size_ = install_plan_.rootfs_size;
diff --git a/filesystem_verifier_action.h b/filesystem_verifier_action.h
index 9935dea..7a6930e 100644
--- a/filesystem_verifier_action.h
+++ b/filesystem_verifier_action.h
@@ -45,6 +45,9 @@
   kKernel,
 };
 
+// Return the partition name string for the passed partition type.
+std::string PartitionTypeToString(const PartitionType partition_type);
+
 class FilesystemVerifierAction : public InstallPlanAction {
  public:
   FilesystemVerifierAction(SystemState* system_state,
diff --git a/filesystem_verifier_action_unittest.cc b/filesystem_verifier_action_unittest.cc
index 37cdf38..32f3c59 100644
--- a/filesystem_verifier_action_unittest.cc
+++ b/filesystem_verifier_action_unittest.cc
@@ -131,11 +131,6 @@
 bool FilesystemVerifierActionTest::DoTest(bool terminate_early,
                                           bool hash_fail,
                                           PartitionType partition_type) {
-  // We need MockHardware to verify MarkUnbootable calls, but don't want
-  // warnings about other usages.
-  testing::NiceMock<MockHardware> mock_hardware;
-  fake_system_state_.set_hardware(&mock_hardware);
-
   string a_loop_file;
 
   if (!(utils::MakeTempFile("a_loop_file.XXXXXX", &a_loop_file, nullptr))) {
@@ -170,6 +165,8 @@
 
   // Set up the action objects
   InstallPlan install_plan;
+  install_plan.source_slot = 0;
+  install_plan.target_slot = 1;
   switch (partition_type) {
     case PartitionType::kRootfs:
       install_plan.rootfs_size = kLoopFileSize - (hash_fail ? 1 : 0);
@@ -207,9 +204,8 @@
       break;
   }
 
-  EXPECT_CALL(mock_hardware,
-              MarkKernelUnbootable(a_dev)).Times(
-                  partition_type == PartitionType::kKernel ? 1 : 0);
+  fake_system_state_.fake_boot_control()->SetSlotBootable(
+    install_plan.target_slot, true);
 
   ActionProcessor processor;
 
@@ -269,12 +265,12 @@
   success = success && is_install_plan_eq;
 
   LOG(INFO) << "Verifying bootable flag on: " << a_dev;
-  bool bootable;
-  EXPECT_TRUE(mock_hardware.fake().IsKernelBootable(a_dev, &bootable));
+
   // We should always mark a partition as unbootable if it's a kernel
   // partition, but never if it's anything else.
-  EXPECT_EQ(bootable, (partition_type != PartitionType::kKernel));
-
+  EXPECT_EQ((partition_type != PartitionType::kKernel),
+            fake_system_state_.fake_boot_control()->IsSlotBootable(
+                install_plan.target_slot));
   return success;
 }
 
diff --git a/hardware.cc b/hardware.cc
index bdc1d41..96edaa5 100644
--- a/hardware.cc
+++ b/hardware.cc
@@ -20,7 +20,6 @@
 #include <base/logging.h>
 #include <base/strings/string_number_conversions.h>
 #include <base/strings/string_util.h>
-#include <rootdev/rootdev.h>
 #include <vboot/crossystem.h>
 
 extern "C" {
@@ -48,116 +47,6 @@
 
 namespace chromeos_update_engine {
 
-Hardware::Hardware() {}
-
-Hardware::~Hardware() {}
-
-string Hardware::BootKernelDevice() const {
-  return utils::KernelDeviceOfBootDevice(Hardware::BootDevice());
-}
-
-string Hardware::BootDevice() const {
-  char boot_path[PATH_MAX];
-  // Resolve the boot device path fully, including dereferencing
-  // through dm-verity.
-  int ret = rootdev(boot_path, sizeof(boot_path), true, false);
-
-  if (ret < 0) {
-    LOG(ERROR) << "rootdev failed to find the root device";
-    return "";
-  }
-  LOG_IF(WARNING, ret > 0) << "rootdev found a device name with no device node";
-
-  // This local variable is used to construct the return string and is not
-  // passed around after use.
-  return boot_path;
-}
-
-bool Hardware::IsBootDeviceRemovable() const {
-  return utils::IsRemovableDevice(utils::GetDiskName(BootDevice()));
-}
-
-bool Hardware::IsKernelBootable(const string& kernel_device,
-                                bool* bootable) const {
-  CgptAddParams params;
-  memset(&params, '\0', sizeof(params));
-
-  string disk_name;
-  int partition_num = 0;
-
-  if (!utils::SplitPartitionName(kernel_device, &disk_name, &partition_num))
-    return false;
-
-  params.drive_name = const_cast<char *>(disk_name.c_str());
-  params.partition = partition_num;
-
-  int retval = CgptGetPartitionDetails(&params);
-  if (retval != CGPT_OK)
-    return false;
-
-  *bootable = params.successful || (params.tries > 0);
-  return true;
-}
-
-vector<string> Hardware::GetKernelDevices() const {
-  LOG(INFO) << "GetAllKernelDevices";
-
-  string disk_name = utils::GetDiskName(Hardware::BootKernelDevice());
-  if (disk_name.empty()) {
-    LOG(ERROR) << "Failed to get the current kernel boot disk name";
-    return vector<string>();
-  }
-
-  vector<string> devices;
-  for (int partition_num : {2, 4}) {  // for now, only #2, #4 for slot A & B
-    string device = utils::MakePartitionName(disk_name, partition_num);
-    if (!device.empty()) {
-      devices.push_back(std::move(device));
-    } else {
-      LOG(ERROR) << "Cannot make a partition name for disk: "
-                 << disk_name << ", partition: " << partition_num;
-    }
-  }
-
-  return devices;
-}
-
-
-bool Hardware::MarkKernelUnbootable(const string& kernel_device) {
-  LOG(INFO) << "MarkPartitionUnbootable: " << kernel_device;
-
-  if (kernel_device == BootKernelDevice()) {
-    LOG(ERROR) << "Refusing to mark current kernel as unbootable.";
-    return false;
-  }
-
-  string disk_name;
-  int partition_num = 0;
-
-  if (!utils::SplitPartitionName(kernel_device, &disk_name, &partition_num))
-    return false;
-
-  CgptAddParams params;
-  memset(&params, 0, sizeof(params));
-
-  params.drive_name = const_cast<char *>(disk_name.c_str());
-  params.partition = partition_num;
-
-  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() const {
   return VbGetSystemPropertyInt("debug_build") == 0;
 }
diff --git a/hardware.h b/hardware.h
index 0e0205c..4f03cf1 100644
--- a/hardware.h
+++ b/hardware.h
@@ -29,17 +29,10 @@
 // Implements the real interface with the hardware.
 class Hardware : public HardwareInterface {
  public:
-  Hardware();
-  ~Hardware() override;
+  Hardware() = default;
+  ~Hardware() override = default;
 
   // HardwareInterface methods.
-  std::string BootKernelDevice() const override;
-  std::string BootDevice() const override;
-  bool IsBootDeviceRemovable() const override;
-  std::vector<std::string> GetKernelDevices() const override;
-  bool IsKernelBootable(const std::string& kernel_device,
-                        bool* bootable) const override;
-  bool MarkKernelUnbootable(const std::string& kernel_device) override;
   bool IsOfficialBuild() const override;
   bool IsNormalBootMode() const override;
   bool IsOOBEComplete(base::Time* out_time_of_oobe) const override;
diff --git a/hardware_interface.h b/hardware_interface.h
index 2010384..7dc4e53 100644
--- a/hardware_interface.h
+++ b/hardware_interface.h
@@ -24,38 +24,14 @@
 
 namespace chromeos_update_engine {
 
-// The hardware interface allows access to the following parts of the system,
-// closely related to the hardware:
-//  * crossystem exposed properties: firmware, hwid, etc.
-//  * Physical disk: partition booted from and partition name conversions.
+// The hardware interface allows access to the crossystem exposed properties,
+// such as the firmware version, hwid, verified boot mode.
 // These stateless functions are tied together in this interface to facilitate
 // unit testing.
 class HardwareInterface {
  public:
   virtual ~HardwareInterface() {}
 
-  // Returns the currently booted kernel partition. "/dev/sda2", for example.
-  virtual std::string BootKernelDevice() const = 0;
-
-  // Returns the currently booted rootfs partition. "/dev/sda3", for example.
-  virtual std::string BootDevice() const = 0;
-
-  // Return whether the BootDevice() is a removable device.
-  virtual bool IsBootDeviceRemovable() const = 0;
-
-  // Returns a list of all kernel partitions available (whether bootable or not)
-  virtual std::vector<std::string> GetKernelDevices() const = 0;
-
-  // Is the specified kernel partition currently bootable, based on GPT flags?
-  // Returns success.
-  virtual bool IsKernelBootable(const std::string& kernel_device,
-                                bool* bootable) const = 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() const = 0;
 
diff --git a/install_plan.cc b/install_plan.cc
index 59fc45c..c378860 100644
--- a/install_plan.cc
+++ b/install_plan.cc
@@ -24,6 +24,9 @@
 
 namespace chromeos_update_engine {
 
+const char* kLegacyPartitionNameKernel = "KERNEL";
+const char* kLegacyPartitionNameRoot = "ROOT";
+
 InstallPlan::InstallPlan(bool is_resume,
                          bool is_full_update,
                          const string& url,
@@ -53,15 +56,6 @@
       powerwash_required(false),
       public_key_rsa(public_key_rsa) {}
 
-InstallPlan::InstallPlan() : is_resume(false),
-                             is_full_update(false),  // play it safe.
-                             payload_size(0),
-                             metadata_size(0),
-                             kernel_size(0),
-                             rootfs_size(0),
-                             hash_checks_mandatory(false),
-                             powerwash_required(false) {}
-
 
 bool InstallPlan::operator==(const InstallPlan& that) const {
   return ((is_resume == that.is_resume) &&
@@ -71,6 +65,8 @@
           (payload_hash == that.payload_hash) &&
           (metadata_size == that.metadata_size) &&
           (metadata_signature == that.metadata_signature) &&
+          (source_slot == that.source_slot) &&
+          (target_slot == that.target_slot) &&
           (install_path == that.install_path) &&
           (kernel_install_path == that.kernel_install_path) &&
           (source_path == that.source_path) &&
@@ -85,6 +81,8 @@
   LOG(INFO) << "InstallPlan: "
             << (is_resume ? "resume" : "new_update")
             << ", payload type: " << (is_full_update ? "full" : "delta")
+            << ", source_slot: " << BootControlInterface::SlotName(source_slot)
+            << ", target_slot: " << BootControlInterface::SlotName(target_slot)
             << ", url: " << download_url
             << ", payload size: " << payload_size
             << ", payload hash: " << payload_hash
@@ -100,4 +98,28 @@
                 powerwash_required);
 }
 
+bool InstallPlan::LoadPartitionsFromSlots(SystemState* system_state) {
+  bool result = true;
+  if (source_slot != BootControlInterface::kInvalidSlot) {
+    result = system_state->boot_control()->GetPartitionDevice(
+        kLegacyPartitionNameRoot, source_slot, &source_path) && result;
+    result = system_state->boot_control()->GetPartitionDevice(
+        kLegacyPartitionNameKernel, source_slot, &kernel_source_path) && result;
+  } else {
+    source_path.clear();
+    kernel_source_path.clear();
+  }
+
+  if (target_slot != BootControlInterface::kInvalidSlot) {
+    result = system_state->boot_control()->GetPartitionDevice(
+        kLegacyPartitionNameRoot, target_slot, &install_path) && result;
+    result = system_state->boot_control()->GetPartitionDevice(
+        kLegacyPartitionNameKernel, target_slot, &kernel_install_path) && result;
+  } else {
+    install_path.clear();
+    kernel_install_path.clear();
+  }
+  return result;
+}
+
 }  // namespace chromeos_update_engine
diff --git a/install_plan.h b/install_plan.h
index b5a5be1..6e6f7ae 100644
--- a/install_plan.h
+++ b/install_plan.h
@@ -24,11 +24,18 @@
 #include <chromeos/secure_blob.h>
 
 #include "update_engine/action.h"
+#include "update_engine/boot_control_interface.h"
+#include "update_engine/system_state.h"
 
 // InstallPlan is a simple struct that contains relevant info for many
 // parts of the update system about the install that should happen.
 namespace chromeos_update_engine {
 
+// TODO(deymo): Remove these constants from this interface once the InstallPlan
+// doesn't list the partitions explicitly.
+extern const char* kLegacyPartitionNameKernel;
+extern const char* kLegacyPartitionNameRoot;
+
 struct InstallPlan {
   InstallPlan(bool is_resume,
               bool is_full_update,
@@ -43,24 +50,31 @@
               const std::string& kernel_source_path,
               const std::string& public_key_rsa);
 
-  // Default constructor: Initialize all members which don't have a class
-  // initializer.
-  InstallPlan();
+  // Default constructor.
+  InstallPlan() = default;
 
   bool operator==(const InstallPlan& that) const;
   bool operator!=(const InstallPlan& that) const;
 
   void Dump() const;
 
-  bool is_resume;
-  bool is_full_update;
+  bool LoadPartitionsFromSlots(SystemState* system_state);
+
+  bool is_resume{false};
+  bool is_full_update{false};
   std::string download_url;  // url to download from
   std::string version;       // version we are installing.
 
-  uint64_t payload_size;                 // size of the payload
-  std::string payload_hash;             // SHA256 hash of the payload
-  uint64_t metadata_size;                // size of the metadata
+  uint64_t payload_size{0};              // size of the payload
+  std::string payload_hash;              // SHA256 hash of the payload
+  uint64_t metadata_size{0};             // size of the metadata
   std::string metadata_signature;        // signature of the  metadata
+
+  // The partition slots used for the update.
+  BootControlInterface::Slot source_slot{BootControlInterface::kInvalidSlot};
+  BootControlInterface::Slot target_slot{BootControlInterface::kInvalidSlot};
+
+  // TODO(deymo): Deprecate these fields and use the slots instead.
   std::string install_path;              // path to install device
   std::string kernel_install_path;       // path to kernel install device
   std::string source_path;               // path to source device
@@ -77,8 +91,8 @@
   //
   // 3. FilesystemVerifierAction computes and verifies the applied and source
   // partition sizes and hashes against the expected values.
-  uint64_t kernel_size;
-  uint64_t rootfs_size;
+  uint64_t kernel_size{0};
+  uint64_t rootfs_size{0};
   chromeos::Blob kernel_hash;
   chromeos::Blob rootfs_hash;
   chromeos::Blob source_kernel_hash;
@@ -86,11 +100,11 @@
 
   // True if payload hash checks are mandatory based on the system state and
   // the Omaha response.
-  bool hash_checks_mandatory;
+  bool hash_checks_mandatory{false};
 
   // True if Powerwash is required on reboot after applying the payload.
   // False otherwise.
-  bool powerwash_required;
+  bool powerwash_required{false};
 
   // If not blank, a base-64 encoded representation of the PEM-encoded
   // public key in the response.
diff --git a/mock_hardware.h b/mock_hardware.h
index 1f22c4f..5cdccb9 100644
--- a/mock_hardware.h
+++ b/mock_hardware.h
@@ -31,24 +31,6 @@
  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, IsBootDeviceRemovable())
-      .WillByDefault(testing::Invoke(&fake_,
-            &FakeHardware::IsBootDeviceRemovable));
-    ON_CALL(*this, GetKernelDevices())
-      .WillByDefault(testing::Invoke(&fake_,
-            &FakeHardware::GetKernelDevices));
-    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));
@@ -72,17 +54,9 @@
             &FakeHardware::GetPowerwashCount));
   }
 
-  ~MockHardware() override {}
+  ~MockHardware() override = default;
 
   // Hardware overrides.
-  MOCK_CONST_METHOD0(BootKernelDevice, std::string());
-  MOCK_CONST_METHOD0(BootDevice, std::string());
-  MOCK_CONST_METHOD0(IsBootDeviceRemovable, bool());
-  MOCK_CONST_METHOD0(GetKernelDevices, std::vector<std::string>());
-  MOCK_CONST_METHOD2(IsKernelBootable,
-               bool(const std::string& kernel_device, bool* bootable));
-  MOCK_METHOD1(MarkKernelUnbootable,
-               bool(const std::string& kernel_device));
   MOCK_CONST_METHOD0(IsOfficialBuild, bool());
   MOCK_CONST_METHOD0(IsNormalBootMode, bool());
   MOCK_CONST_METHOD1(IsOOBEComplete, bool(base::Time* out_time_of_oobe));
diff --git a/omaha_response_handler_action.cc b/omaha_response_handler_action.cc
index f7afc77..dba3d74 100644
--- a/omaha_response_handler_action.cc
+++ b/omaha_response_handler_action.cc
@@ -111,20 +111,13 @@
   }
   install_plan_.is_full_update = !response.is_delta_payload;
 
-  TEST_AND_RETURN(utils::GetInstallDev(
-      (!boot_device_.empty() ? boot_device_ :
-          system_state_->hardware()->BootDevice()),
-      &install_plan_.install_path));
-  install_plan_.kernel_install_path =
-      utils::KernelDeviceOfBootDevice(install_plan_.install_path);
-  install_plan_.source_path = system_state_->hardware()->BootDevice();
-  install_plan_.kernel_source_path =
-      utils::KernelDeviceOfBootDevice(install_plan_.source_path);
+  install_plan_.source_slot = system_state_->boot_control()->GetCurrentSlot();
+  install_plan_.target_slot = install_plan_.source_slot == 0 ? 1 : 0;
+  TEST_AND_RETURN(install_plan_.LoadPartitionsFromSlots(system_state_));
 
   if (params->to_more_stable_channel() && params->is_powerwash_allowed())
     install_plan_.powerwash_required = true;
 
-
   TEST_AND_RETURN(HasOutputPipe());
   if (HasOutputPipe())
     SetOutputObject(install_plan_);
diff --git a/omaha_response_handler_action.h b/omaha_response_handler_action.h
index f20c9b4..5611375 100644
--- a/omaha_response_handler_action.h
+++ b/omaha_response_handler_action.h
@@ -56,11 +56,6 @@
   // never be called
   void TerminateProcessing() override { CHECK(false); }
 
-  // For unit-testing
-  void set_boot_device(const std::string& boot_device) {
-    boot_device_ = boot_device;
-  }
-
   bool GotNoUpdateResponse() const { return got_no_update_response_; }
   const InstallPlan& install_plan() const { return install_plan_; }
 
@@ -77,9 +72,6 @@
   // Global system context.
   SystemState* system_state_;
 
-  // set to non-empty in unit tests
-  std::string boot_device_;
-
   // The install plan, if we have an update.
   InstallPlan install_plan_;
 
diff --git a/omaha_response_handler_action_unittest.cc b/omaha_response_handler_action_unittest.cc
index 1cf8d25..20d0166 100644
--- a/omaha_response_handler_action_unittest.cc
+++ b/omaha_response_handler_action_unittest.cc
@@ -34,18 +34,26 @@
 namespace chromeos_update_engine {
 
 class OmahaResponseHandlerActionTest : public ::testing::Test {
- public:
+ protected:
+  void SetUp() override {
+    FakeBootControl* fake_boot_control = fake_system_state_.fake_boot_control();
+    fake_boot_control->SetPartitionDevice(
+        kLegacyPartitionNameKernel, 0, "/dev/sdz2");
+    fake_boot_control->SetPartitionDevice(
+        kLegacyPartitionNameRoot, 0, "/dev/sdz3");
+    fake_boot_control->SetPartitionDevice(
+        kLegacyPartitionNameKernel, 1, "/dev/sdz4");
+    fake_boot_control->SetPartitionDevice(
+        kLegacyPartitionNameRoot, 1, "/dev/sdz5");
+  }
+
   // Return true iff the OmahaResponseHandlerAction succeeded.
   // If out is non-null, it's set w/ the response from the action.
-  bool DoTestCommon(FakeSystemState* fake_system_state,
-                    const OmahaResponse& in,
-                    const string& boot_dev,
-                    const string& deadline_file,
-                    InstallPlan* out);
   bool DoTest(const OmahaResponse& in,
-              const string& boot_dev,
               const string& deadline_file,
               InstallPlan* out);
+
+  FakeSystemState fake_system_state_;
 };
 
 class OmahaResponseHandlerActionProcessorDelegate
@@ -79,10 +87,8 @@
 const char* const kBadVersion = "don't update me";
 }  // namespace
 
-bool OmahaResponseHandlerActionTest::DoTestCommon(
-    FakeSystemState* fake_system_state,
+bool OmahaResponseHandlerActionTest::DoTest(
     const OmahaResponse& in,
-    const string& boot_dev,
     const string& test_deadline_file,
     InstallPlan* out) {
   ActionProcessor processor;
@@ -92,20 +98,19 @@
   ObjectFeederAction<OmahaResponse> feeder_action;
   feeder_action.set_obj(in);
   if (in.update_exists && in.version != kBadVersion) {
-    EXPECT_CALL(*(fake_system_state->mock_prefs()),
+    EXPECT_CALL(*(fake_system_state_.mock_prefs()),
                 SetString(kPrefsUpdateCheckResponseHash, in.hash))
         .WillOnce(Return(true));
   }
 
   string current_url = in.payload_urls.size() ? in.payload_urls[0] : "";
-  EXPECT_CALL(*(fake_system_state->mock_payload_state()), GetCurrentUrl())
+  EXPECT_CALL(*(fake_system_state_.mock_payload_state()), GetCurrentUrl())
       .WillRepeatedly(Return(current_url));
 
   OmahaResponseHandlerAction response_handler_action(
-      fake_system_state,
+      &fake_system_state_,
       (test_deadline_file.empty() ?
        OmahaResponseHandlerAction::kDeadlineFile : test_deadline_file));
-  response_handler_action.set_boot_device(boot_dev);
   BondActions(&feeder_action, &response_handler_action);
   ObjectCollectorAction<InstallPlan> collector_action;
   BondActions(&response_handler_action, &collector_action);
@@ -121,14 +126,6 @@
   return delegate.code_ == ErrorCode::kSuccess;
 }
 
-bool OmahaResponseHandlerActionTest::DoTest(const OmahaResponse& in,
-                                            const string& boot_dev,
-                                            const string& deadline_file,
-                                            InstallPlan* out) {
-  FakeSystemState fake_system_state;
-  return DoTestCommon(&fake_system_state, in, boot_dev, deadline_file, out);
-}
-
 TEST_F(OmahaResponseHandlerActionTest, SimpleTest) {
   string test_deadline_file;
   CHECK(utils::MakeTempFile(
@@ -146,10 +143,10 @@
     in.prompt = false;
     in.deadline = "20101020";
     InstallPlan install_plan;
-    EXPECT_TRUE(DoTest(in, "/dev/sda3", test_deadline_file, &install_plan));
+    EXPECT_TRUE(DoTest(in, test_deadline_file, &install_plan));
     EXPECT_EQ(in.payload_urls[0], install_plan.download_url);
     EXPECT_EQ(in.hash, install_plan.payload_hash);
-    EXPECT_EQ("/dev/sda5", install_plan.install_path);
+    EXPECT_EQ(1, install_plan.target_slot);
     string deadline;
     EXPECT_TRUE(utils::ReadFile(test_deadline_file, &deadline));
     EXPECT_EQ("20101020", deadline);
@@ -169,10 +166,12 @@
     in.size = 12;
     in.prompt = true;
     InstallPlan install_plan;
-    EXPECT_TRUE(DoTest(in, "/dev/sda5", test_deadline_file, &install_plan));
+    // Set the other slot as current.
+    fake_system_state_.fake_boot_control()->SetCurrentSlot(1);
+    EXPECT_TRUE(DoTest(in, test_deadline_file, &install_plan));
     EXPECT_EQ(in.payload_urls[0], install_plan.download_url);
     EXPECT_EQ(in.hash, install_plan.payload_hash);
-    EXPECT_EQ("/dev/sda3", install_plan.install_path);
+    EXPECT_EQ(0, install_plan.target_slot);
     string deadline;
     EXPECT_TRUE(utils::ReadFile(test_deadline_file, &deadline) &&
                 deadline.empty());
@@ -189,10 +188,11 @@
     in.prompt = true;
     in.deadline = "some-deadline";
     InstallPlan install_plan;
-    EXPECT_TRUE(DoTest(in, "/dev/sda3", test_deadline_file, &install_plan));
+    fake_system_state_.fake_boot_control()->SetCurrentSlot(0);
+    EXPECT_TRUE(DoTest(in, test_deadline_file, &install_plan));
     EXPECT_EQ(in.payload_urls[0], install_plan.download_url);
     EXPECT_EQ(in.hash, install_plan.payload_hash);
-    EXPECT_EQ("/dev/sda5", install_plan.install_path);
+    EXPECT_EQ(1, install_plan.target_slot);
     string deadline;
     EXPECT_TRUE(utils::ReadFile(test_deadline_file, &deadline));
     EXPECT_EQ("some-deadline", deadline);
@@ -204,7 +204,7 @@
   OmahaResponse in;
   in.update_exists = false;
   InstallPlan install_plan;
-  EXPECT_FALSE(DoTest(in, "/dev/sda1", "", &install_plan));
+  EXPECT_FALSE(DoTest(in, "", &install_plan));
   EXPECT_EQ("", install_plan.download_url);
   EXPECT_EQ("", install_plan.payload_hash);
   EXPECT_EQ("", install_plan.install_path);
@@ -219,14 +219,12 @@
   in.more_info_url = "http://more/info";
   in.hash = "HASHj+";
   in.size = 12;
-  FakeSystemState fake_system_state;
   // Hash checks are always skipped for non-official update URLs.
-  EXPECT_CALL(*(fake_system_state.mock_request_params()),
+  EXPECT_CALL(*(fake_system_state_.mock_request_params()),
               IsUpdateUrlOfficial())
       .WillRepeatedly(Return(true));
   InstallPlan install_plan;
-  EXPECT_TRUE(DoTestCommon(&fake_system_state, in, "/dev/sda5", "",
-                           &install_plan));
+  EXPECT_TRUE(DoTest(in, "", &install_plan));
   EXPECT_EQ(in.payload_urls[0], install_plan.download_url);
   EXPECT_EQ(in.hash, install_plan.payload_hash);
   EXPECT_TRUE(install_plan.hash_checks_mandatory);
@@ -241,13 +239,11 @@
   in.more_info_url = "http://more/info";
   in.hash = "HASHj+";
   in.size = 12;
-  FakeSystemState fake_system_state;
-  EXPECT_CALL(*(fake_system_state.mock_request_params()),
+  EXPECT_CALL(*(fake_system_state_.mock_request_params()),
               IsUpdateUrlOfficial())
       .WillRepeatedly(Return(false));
   InstallPlan install_plan;
-  EXPECT_TRUE(DoTestCommon(&fake_system_state, in, "/dev/sda5", "",
-                           &install_plan));
+  EXPECT_TRUE(DoTest(in, "", &install_plan));
   EXPECT_EQ(in.payload_urls[0], install_plan.download_url);
   EXPECT_EQ(in.hash, install_plan.payload_hash);
   EXPECT_FALSE(install_plan.hash_checks_mandatory);
@@ -264,14 +260,12 @@
   in.more_info_url = "http://more/info";
   in.hash = "HASHj+";
   in.size = 12;
-  FakeSystemState fake_system_state;
-  EXPECT_CALL(*(fake_system_state.mock_request_params()),
+  EXPECT_CALL(*(fake_system_state_.mock_request_params()),
               IsUpdateUrlOfficial())
       .WillRepeatedly(Return(true));
-  fake_system_state.fake_hardware()->SetIsOfficialBuild(false);
+  fake_system_state_.fake_hardware()->SetIsOfficialBuild(false);
   InstallPlan install_plan;
-  EXPECT_TRUE(DoTestCommon(&fake_system_state, in, "/dev/sda5", "",
-                           &install_plan));
+  EXPECT_TRUE(DoTest(in, "", &install_plan));
   EXPECT_EQ(in.payload_urls[0], install_plan.download_url);
   EXPECT_EQ(in.hash, install_plan.payload_hash);
   EXPECT_FALSE(install_plan.hash_checks_mandatory);
@@ -286,13 +280,11 @@
   in.more_info_url = "http://more/info";
   in.hash = "HASHj+";
   in.size = 12;
-  FakeSystemState fake_system_state;
-  EXPECT_CALL(*(fake_system_state.mock_request_params()),
+  EXPECT_CALL(*(fake_system_state_.mock_request_params()),
               IsUpdateUrlOfficial())
       .WillRepeatedly(Return(true));
   InstallPlan install_plan;
-  EXPECT_TRUE(DoTestCommon(&fake_system_state, in, "/dev/sda5", "",
-                           &install_plan));
+  EXPECT_TRUE(DoTest(in, "", &install_plan));
   EXPECT_EQ(in.payload_urls[0], install_plan.download_url);
   EXPECT_EQ(in.hash, install_plan.payload_hash);
   EXPECT_FALSE(install_plan.hash_checks_mandatory);
@@ -308,13 +300,11 @@
   in.more_info_url = "http://more/info";
   in.hash = "HASHj+";
   in.size = 12;
-  FakeSystemState fake_system_state;
-  EXPECT_CALL(*(fake_system_state.mock_request_params()),
+  EXPECT_CALL(*(fake_system_state_.mock_request_params()),
               IsUpdateUrlOfficial())
       .WillRepeatedly(Return(true));
   InstallPlan install_plan;
-  EXPECT_TRUE(DoTestCommon(&fake_system_state, in, "/dev/sda5", "",
-                           &install_plan));
+  EXPECT_TRUE(DoTest(in, "", &install_plan));
   EXPECT_EQ(in.payload_urls[0], install_plan.download_url);
   EXPECT_EQ(in.hash, install_plan.payload_hash);
   EXPECT_TRUE(install_plan.hash_checks_mandatory);
@@ -346,8 +336,7 @@
       "CHROMEOS_IS_POWERWASH_ALLOWED=true\n"
       "CHROMEOS_RELEASE_TRACK=stable-channel\n"));
 
-  FakeSystemState fake_system_state;
-  OmahaRequestParams params(&fake_system_state);
+  OmahaRequestParams params(&fake_system_state_);
   params.set_root(test_dir);
   params.SetLockDown(false);
   params.Init("1.2.3.4", "", 0);
@@ -356,10 +345,9 @@
   EXPECT_TRUE(params.to_more_stable_channel());
   EXPECT_TRUE(params.is_powerwash_allowed());
 
-  fake_system_state.set_request_params(&params);
+  fake_system_state_.set_request_params(&params);
   InstallPlan install_plan;
-  EXPECT_TRUE(DoTestCommon(&fake_system_state, in, "/dev/sda5", "",
-                           &install_plan));
+  EXPECT_TRUE(DoTest(in, "", &install_plan));
   EXPECT_TRUE(install_plan.powerwash_required);
 
   ASSERT_TRUE(test_utils::RecursiveUnlinkDir(test_dir));
@@ -389,8 +377,7 @@
       test_dir + kStatefulPartition + "/etc/lsb-release",
       "CHROMEOS_RELEASE_TRACK=canary-channel\n"));
 
-  FakeSystemState fake_system_state;
-  OmahaRequestParams params(&fake_system_state);
+  OmahaRequestParams params(&fake_system_state_);
   params.set_root(test_dir);
   params.SetLockDown(false);
   params.Init("5.6.7.8", "", 0);
@@ -400,10 +387,9 @@
   EXPECT_FALSE(params.to_more_stable_channel());
   EXPECT_FALSE(params.is_powerwash_allowed());
 
-  fake_system_state.set_request_params(&params);
+  fake_system_state_.set_request_params(&params);
   InstallPlan install_plan;
-  EXPECT_TRUE(DoTestCommon(&fake_system_state, in, "/dev/sda5", "",
-                           &install_plan));
+  EXPECT_TRUE(DoTest(in, "", &install_plan));
   EXPECT_FALSE(install_plan.powerwash_required);
 
   ASSERT_TRUE(test_utils::RecursiveUnlinkDir(test_dir));
@@ -418,26 +404,24 @@
   in.hash = "HASHj+";
   in.size = 12;
 
-  FakeSystemState fake_system_state;
-  OmahaRequestParams params(&fake_system_state);
+  OmahaRequestParams params(&fake_system_state_);
   // We're using a real OmahaRequestParams object here so we can't mock
   // IsUpdateUrlOfficial(), but setting the update URL to the AutoUpdate test
   // server will cause IsUpdateUrlOfficial() to return true.
   params.set_update_url(kAUTestOmahaUrl);
-  fake_system_state.set_request_params(&params);
+  fake_system_state_.set_request_params(&params);
 
-  EXPECT_CALL(*fake_system_state.mock_payload_state(),
+  EXPECT_CALL(*fake_system_state_.mock_payload_state(),
               SetUsingP2PForDownloading(true));
 
   string p2p_url = "http://9.8.7.6/p2p";
-  EXPECT_CALL(*fake_system_state.mock_payload_state(), GetP2PUrl())
+  EXPECT_CALL(*fake_system_state_.mock_payload_state(), GetP2PUrl())
       .WillRepeatedly(Return(p2p_url));
-  EXPECT_CALL(*fake_system_state.mock_payload_state(),
+  EXPECT_CALL(*fake_system_state_.mock_payload_state(),
               GetUsingP2PForDownloading()).WillRepeatedly(Return(true));
 
   InstallPlan install_plan;
-  EXPECT_TRUE(DoTestCommon(&fake_system_state, in, "/dev/sda5", "",
-                           &install_plan));
+  EXPECT_TRUE(DoTest(in, "", &install_plan));
   EXPECT_EQ(in.hash, install_plan.payload_hash);
   EXPECT_EQ(install_plan.download_url, p2p_url);
   EXPECT_TRUE(install_plan.hash_checks_mandatory);
diff --git a/payload_state.cc b/payload_state.cc
index e7904c1..78c048b 100644
--- a/payload_state.cc
+++ b/payload_state.cc
@@ -1422,8 +1422,14 @@
       LOG(ERROR) << "Error reading TargetVersionInstalledFrom on reboot.";
       return;
     }
-    if (static_cast<int>(installed_from) ==
-        utils::GetPartitionNumber(system_state_->hardware()->BootDevice())) {
+    // Old Chrome OS devices will write 2 or 4 in this setting, with the
+    // partition number. We are now using slot numbers (0 or 1) instead, so
+    // the following comparison will not match if we are comparing an old
+    // partition number against a new slot number, which is the correct outcome
+    // since we successfully booted the new update in that case. If the boot
+    // failed, we will read this value from the same version, so it will always
+    // be compatible.
+    if (installed_from == system_state_->boot_control()->GetCurrentSlot()) {
       // A reboot was pending, but the chromebook is again in the same
       // BootDevice where the update was installed from.
       int64_t target_attempt;
@@ -1483,8 +1489,7 @@
   prefs_->SetInt64(kPrefsTargetVersionAttempt, target_attempt + 1);
 
   prefs_->SetInt64(kPrefsTargetVersionInstalledFrom,
-                    utils::GetPartitionNumber(
-                        system_state_->hardware()->BootDevice()));
+                   system_state_->boot_control()->GetCurrentSlot());
 }
 
 void PayloadState::ResetUpdateStatus() {
@@ -1496,7 +1501,7 @@
   // Also decrement the attempt number if it exists.
   int64_t target_attempt;
   if (prefs_->GetInt64(kPrefsTargetVersionAttempt, &target_attempt))
-    prefs_->SetInt64(kPrefsTargetVersionAttempt, target_attempt-1);
+    prefs_->SetInt64(kPrefsTargetVersionAttempt, target_attempt - 1);
 }
 
 int PayloadState::GetP2PNumAttempts() {
diff --git a/payload_state_unittest.cc b/payload_state_unittest.cc
index aea5389..e4eca89 100644
--- a/payload_state_unittest.cc
+++ b/payload_state_unittest.cc
@@ -1492,9 +1492,6 @@
   FakePrefs fake_prefs;
   fake_system_state.set_prefs(&fake_prefs);
 
-  FakeHardware* fake_hardware = fake_system_state.fake_hardware();
-  fake_hardware->SetBootDevice("/dev/sda3");
-
   EXPECT_TRUE(payload_state.Initialize(&fake_system_state));
   SetupPayloadStateWith2Urls("Hash3141", true, &payload_state, &response);
 
@@ -1540,8 +1537,8 @@
   FakePrefs fake_prefs;
   fake_system_state.set_prefs(&fake_prefs);
 
-  FakeHardware* fake_hardware = fake_system_state.fake_hardware();
-  fake_hardware->SetBootDevice("/dev/sda3");
+  FakeBootControl* fake_boot_control = fake_system_state.fake_boot_control();
+  fake_boot_control->SetCurrentSlot(0);
 
   EXPECT_TRUE(payload_state.Initialize(&fake_system_state));
   SetupPayloadStateWith2Urls("Hash3141", true, &payload_state, &response);
@@ -1552,7 +1549,7 @@
   payload_state.ExpectRebootInNewVersion("Version:12345678");
 
   // Change the BootDevice to a different one, no metric should be sent.
-  fake_hardware->SetBootDevice("/dev/sda5");
+  fake_boot_control->SetCurrentSlot(1);
 
   EXPECT_CALL(*fake_system_state.mock_metrics_lib(), SendToUMA(
       "Installer.RebootToNewPartitionAttempt", _, _, _, _))
@@ -1562,9 +1559,9 @@
       .Times(0);
   payload_state.ReportFailedBootIfNeeded();
 
-  // A second reboot in eiher partition should not send a metric.
+  // A second reboot in either partition should not send a metric.
   payload_state.ReportFailedBootIfNeeded();
-  fake_hardware->SetBootDevice("/dev/sda3");
+  fake_boot_control->SetCurrentSlot(0);
   payload_state.ReportFailedBootIfNeeded();
 }
 
diff --git a/real_system_state.cc b/real_system_state.cc
index d374cc1..5477dd6 100644
--- a/real_system_state.cc
+++ b/real_system_state.cc
@@ -20,6 +20,7 @@
 #include <base/time/time.h>
 #include <chromeos/dbus/service_constants.h>
 
+#include "update_engine/boot_control_chromeos.h"
 #include "update_engine/constants.h"
 #include "update_engine/update_manager/state_factory.h"
 #include "update_engine/utils.h"
@@ -37,6 +38,13 @@
 bool RealSystemState::Initialize() {
   metrics_lib_.Init();
 
+  // TODO(deymo): Initialize BootControl based on the build environment.
+  BootControlChromeOS* boot_control_cros = new BootControlChromeOS();
+  boot_control_.reset(boot_control_cros);
+  if (!boot_control_cros->Init()) {
+    LOG(ERROR) << "Ignoring BootControlChromeOS failure. We won't run updates.";
+  }
+
   if (!shill_proxy_.Init()) {
     LOG(ERROR) << "Failed to initialize shill proxy.";
     return false;
diff --git a/real_system_state.h b/real_system_state.h
index 9eaa6cb..a51e6ef 100644
--- a/real_system_state.h
+++ b/real_system_state.h
@@ -27,6 +27,7 @@
 #include "debugd/dbus-proxies.h"
 #include "login_manager/dbus-proxies.h"
 #include "power_manager/dbus-proxies.h"
+#include "update_engine/boot_control_interface.h"
 #include "update_engine/clock.h"
 #include "update_engine/connection_manager.h"
 #include "update_engine/hardware.h"
@@ -60,6 +61,10 @@
     return device_policy_;
   }
 
+  inline BootControlInterface* boot_control() override {
+    return boot_control_.get();
+  }
+
   inline ClockInterface* clock() override { return &clock_; }
 
   inline ConnectionManagerInterface* connection_manager() override {
@@ -112,6 +117,9 @@
   LibCrosProxy libcros_proxy_;
 
   // Interface for the clock.
+  std::unique_ptr<BootControlInterface> boot_control_;
+
+  // Interface for the clock.
   Clock clock_;
 
   // The latest device policy object from the policy provider.
diff --git a/system_state.h b/system_state.h
index 4d4c74a..0190ee2 100644
--- a/system_state.h
+++ b/system_state.h
@@ -38,6 +38,7 @@
 // SystemState is the root class within the update engine. So we should avoid
 // any circular references in header file inclusion. Hence forward-declaring
 // the required classes.
+class BootControlInterface;
 class ClockInterface;
 class ConnectionManagerInterface;
 class HardwareInterface;
@@ -63,6 +64,9 @@
   virtual void set_device_policy(const policy::DevicePolicy* device_policy) = 0;
   virtual const policy::DevicePolicy* device_policy() = 0;
 
+  // Gets the interface object for the bootloader control interface.
+  virtual BootControlInterface* boot_control() = 0;
+
   // Gets the interface object for the clock.
   virtual ClockInterface* clock() = 0;
 
diff --git a/update_attempter.cc b/update_attempter.cc
index cfa44bf..82831e4 100644
--- a/update_attempter.cc
+++ b/update_attempter.cc
@@ -735,15 +735,10 @@
   LOG(INFO) << "Setting rollback options.";
   InstallPlan install_plan;
 
-  TEST_AND_RETURN_FALSE(utils::GetInstallDev(
-      system_state_->hardware()->BootDevice(),
-      &install_plan.install_path));
+  install_plan.target_slot = GetRollbackSlot();
+  install_plan.source_slot = system_state_->boot_control()->GetCurrentSlot();
 
-  install_plan.kernel_install_path =
-      utils::KernelDeviceOfBootDevice(install_plan.install_path);
-  install_plan.source_path = system_state_->hardware()->BootDevice();
-  install_plan.kernel_source_path =
-      utils::KernelDeviceOfBootDevice(install_plan.source_path);
+  TEST_AND_RETURN_FALSE(install_plan.LoadPartitionsFromSlots(system_state_));
   install_plan.powerwash_required = powerwash;
 
   LOG(INFO) << "Using this install plan:";
@@ -776,59 +771,53 @@
 bool UpdateAttempter::CanRollback() const {
   // We can only rollback if the update_engine isn't busy and we have a valid
   // rollback partition.
-  return (status_ == UPDATE_STATUS_IDLE && !GetRollbackPartition().empty());
+  return (status_ == UPDATE_STATUS_IDLE &&
+          GetRollbackSlot() != BootControlInterface::kInvalidSlot);
 }
 
-string UpdateAttempter::GetRollbackPartition() const {
-  vector<string> kernel_devices =
-      system_state_->hardware()->GetKernelDevices();
+BootControlInterface::Slot UpdateAttempter::GetRollbackSlot() const {
+  LOG(INFO) << "UpdateAttempter::GetRollbackSlot";
+  const unsigned int num_slots = system_state_->boot_control()->GetNumSlots();
+  const BootControlInterface::Slot current_slot =
+      system_state_->boot_control()->GetCurrentSlot();
 
-  string boot_kernel_device =
-      system_state_->hardware()->BootKernelDevice();
+  LOG(INFO) << "  Installed slots: " << num_slots;
+  LOG(INFO) << "  Booted from slot: "
+            << BootControlInterface::SlotName(current_slot);
 
-  LOG(INFO) << "UpdateAttempter::GetRollbackPartition";
-  for (const auto& name : kernel_devices)
-    LOG(INFO) << "  Available kernel device = " << name;
-  LOG(INFO) << "  Boot kernel device =      " << boot_kernel_device;
-
-  auto current = std::find(kernel_devices.begin(), kernel_devices.end(),
-                           boot_kernel_device);
-
-  if (current == kernel_devices.end()) {
-    LOG(ERROR) << "Unable to find the boot kernel device in the list of "
-               << "available devices";
-    return string();
+  if (current_slot == BootControlInterface::kInvalidSlot || num_slots < 2) {
+    LOG(INFO) << "Device is not updateable.";
+    return BootControlInterface::kInvalidSlot;
   }
 
-  for (string const& device_name : kernel_devices) {
-    if (device_name != *current) {
-      bool bootable = false;
-      if (system_state_->hardware()->IsKernelBootable(device_name, &bootable) &&
-          bootable) {
-        return device_name;
-      }
+  vector<BootControlInterface::Slot> bootable_slots;
+  for(BootControlInterface::Slot slot = 0; slot < num_slots; slot++) {
+    if (slot != current_slot &&
+        system_state_->boot_control()->IsSlotBootable(slot)) {
+      LOG(INFO) << "Found bootable slot "
+                << BootControlInterface::SlotName(slot);
+      return slot;
     }
   }
-
-  return string();
+  LOG(INFO) << "No other bootable slot found.";
+  return BootControlInterface::kInvalidSlot;
 }
 
-vector<std::pair<string, bool>>
-    UpdateAttempter::GetKernelDevices() const {
-  vector<string> kernel_devices =
-    system_state_->hardware()->GetKernelDevices();
-
-  string boot_kernel_device =
-    system_state_->hardware()->BootKernelDevice();
+vector<std::pair<string, bool>> UpdateAttempter::GetKernelDevices() const {
+  const unsigned int num_slots = system_state_->boot_control()->GetNumSlots();
+  const BootControlInterface::Slot current_slot =
+      system_state_->boot_control()->GetCurrentSlot();
 
   vector<std::pair<string, bool>> info_list;
-  info_list.reserve(kernel_devices.size());
-
-  for (string device_name : kernel_devices) {
-    bool bootable = false;
-    system_state_->hardware()->IsKernelBootable(device_name, &bootable);
+  for (BootControlInterface::Slot slot = 0; slot < num_slots; slot++) {
+    bool bootable = system_state_->boot_control()->IsSlotBootable(slot);
+    string device_name;
+    if (!system_state_->boot_control()->GetPartitionDevice(
+            kLegacyPartitionNameKernel, slot, &device_name)) {
+      continue;
+    }
     // Add '*' to the name of the partition we booted from.
-    if (device_name == boot_kernel_device)
+    if (slot == current_slot)
       device_name += '*';
     info_list.emplace_back(device_name, bootable);
   }
diff --git a/update_attempter.h b/update_attempter.h
index 19b7b81..a884924 100644
--- a/update_attempter.h
+++ b/update_attempter.h
@@ -155,7 +155,7 @@
   // This is the internal entry point for getting a rollback partition name,
   // if one exists. It returns the bootable rollback kernel device partition
   // name or empty string if none is available.
-  std::string GetRollbackPartition() const;
+  BootControlInterface::Slot GetRollbackSlot() const;
 
   // Returns a list of available kernel partitions along with information
   // whether it is possible to boot from it.
diff --git a/update_attempter_unittest.cc b/update_attempter_unittest.cc
index a50c50a..546dd03 100644
--- a/update_attempter_unittest.cc
+++ b/update_attempter_unittest.cc
@@ -452,12 +452,21 @@
   EXPECT_CALL(*device_policy, LoadPolicy()).WillRepeatedly(Return(true));
   fake_system_state_.set_device_policy(device_policy);
 
-  if (!valid_slot) {
-    // References bootable kernels in fake_hardware.h
-    string rollback_kernel = "/dev/sdz2";
-    LOG(INFO) << "Test Mark Unbootable: " << rollback_kernel;
-    fake_system_state_.fake_hardware()->MarkKernelUnbootable(
-        rollback_kernel);
+  FakeBootControl* fake_boot_control = fake_system_state_.fake_boot_control();
+  fake_boot_control->SetPartitionDevice(
+      kLegacyPartitionNameKernel, 0, "/dev/sdz2");
+  fake_boot_control->SetPartitionDevice(
+      kLegacyPartitionNameRoot, 0, "/dev/sdz3");
+
+  if (valid_slot) {
+    BootControlInterface::Slot rollback_slot = 1;
+    LOG(INFO) << "Test Mark Bootable: "
+              << BootControlInterface::SlotName(rollback_slot);
+    fake_boot_control->SetSlotBootable(rollback_slot, true);
+    fake_boot_control->SetPartitionDevice(
+        kLegacyPartitionNameKernel, rollback_slot, "/dev/sdz4");
+    fake_boot_control->SetPartitionDevice(
+        kLegacyPartitionNameRoot, rollback_slot, "/dev/sdz5");
   }
 
   bool is_rollback_allowed = false;
@@ -510,10 +519,8 @@
   InstallPlanAction* install_plan_action =
         dynamic_cast<InstallPlanAction*>(attempter_.actions_[0].get());
   InstallPlan* install_plan = install_plan_action->install_plan();
-  // Matches fake_hardware.h -> rollback should move from kernel/boot device
-  // pair to other pair.
-  EXPECT_EQ(install_plan->install_path, string("/dev/sdz3"));
-  EXPECT_EQ(install_plan->kernel_install_path, string("/dev/sdz2"));
+  EXPECT_EQ(install_plan->install_path, string("/dev/sdz5"));
+  EXPECT_EQ(install_plan->kernel_install_path, string("/dev/sdz4"));
   EXPECT_EQ(install_plan->powerwash_required, true);
   loop_.BreakLoop();
 }
diff --git a/update_engine.gyp b/update_engine.gyp
index 2b8c1ac..e0df1e9 100644
--- a/update_engine.gyp
+++ b/update_engine.gyp
@@ -198,6 +198,7 @@
       },
       'sources': [
         'action_processor.cc',
+        'boot_control_chromeos.cc',
         'bzip.cc',
         'bzip_extent_writer.cc',
         'certificate_checker.cc',
@@ -455,6 +456,7 @@
             'action_pipe_unittest.cc',
             'action_processor_unittest.cc',
             'action_unittest.cc',
+            'boot_control_chromeos_unittest.cc',
             'bzip_extent_writer_unittest.cc',
             'certificate_checker_unittest.cc',
             'chrome_browser_proxy_resolver_unittest.cc',
diff --git a/update_manager/chromeos_policy.cc b/update_manager/chromeos_policy.cc
index 80ef63b..1c77318 100644
--- a/update_manager/chromeos_policy.cc
+++ b/update_manager/chromeos_policy.cc
@@ -187,10 +187,10 @@
 
   // Do not perform any updates if booted from removable device. This decision
   // is final.
-  const bool* is_boot_device_removable_p = ec->GetValue(
-      system_provider->var_is_boot_device_removable());
-  if (is_boot_device_removable_p && *is_boot_device_removable_p) {
-    LOG(INFO) << "Booted from removable device, disabling update checks.";
+  const unsigned int* num_slots_p = ec->GetValue(
+      system_provider->var_num_slots());
+  if (!num_slots_p || *num_slots_p < 2) {
+    LOG(INFO) << "Not enough slots for A/B updates, disabling update checks.";
     result->updates_enabled = false;
     return EvalStatus::kSucceeded;
   }
diff --git a/update_manager/chromeos_policy_unittest.cc b/update_manager/chromeos_policy_unittest.cc
index 05a5e4b..e456e19 100644
--- a/update_manager/chromeos_policy_unittest.cc
+++ b/update_manager/chromeos_policy_unittest.cc
@@ -91,8 +91,7 @@
         new bool(true));
     fake_state_.system_provider()->var_is_oobe_complete()->reset(
         new bool(true));
-    fake_state_.system_provider()->var_is_boot_device_removable()->reset(
-        new bool(false));
+    fake_state_.system_provider()->var_num_slots()->reset(new unsigned int(2));
 
     // Connection is wifi, untethered.
     fake_state_.shill_provider()->var_conn_type()->
@@ -421,8 +420,7 @@
   // UpdateCheckAllowed should return false (kSucceeded) if the image booted
   // from a removable device.
 
-  fake_state_.system_provider()->var_is_boot_device_removable()->reset(
-      new bool(true));
+  fake_state_.system_provider()->var_num_slots()->reset(new unsigned int(1));
 
   UpdateCheckParams result;
   ExpectPolicyStatus(EvalStatus::kSucceeded,
diff --git a/update_manager/fake_system_provider.h b/update_manager/fake_system_provider.h
index 5124231..6036198 100644
--- a/update_manager/fake_system_provider.h
+++ b/update_manager/fake_system_provider.h
@@ -39,8 +39,8 @@
     return &var_is_oobe_complete_;
   }
 
-  FakeVariable<bool>* var_is_boot_device_removable() override {
-    return &var_is_boot_device_removable_;
+  FakeVariable<unsigned int>* var_num_slots() override {
+    return &var_num_slots_;
   }
 
  private:
@@ -50,9 +50,7 @@
     "is_official_build", kVariableModeConst};
   FakeVariable<bool> var_is_oobe_complete_{  // NOLINT(whitespace/braces)
     "is_oobe_complete", kVariableModePoll};
-  FakeVariable<bool>
-      var_is_boot_device_removable_{  // NOLINT(whitespace/braces)
-        "is_boot_device_removable", kVariableModePoll};
+  FakeVariable<unsigned int> var_num_slots_{"num_slots", kVariableModePoll};
 
   DISALLOW_COPY_AND_ASSIGN(FakeSystemProvider);
 };
diff --git a/update_manager/real_system_provider.cc b/update_manager/real_system_provider.cc
index abb920e..d0d788d 100644
--- a/update_manager/real_system_provider.cc
+++ b/update_manager/real_system_provider.cc
@@ -51,9 +51,9 @@
           base::Bind(&chromeos_update_engine::HardwareInterface::IsOOBEComplete,
                      base::Unretained(hardware_), nullptr)));
 
-  var_is_boot_device_removable_.reset(
-      new ConstCopyVariable<bool>("is_boot_device_removable",
-                                  hardware_->IsBootDeviceRemovable()));
+  var_num_slots_.reset(
+      new ConstCopyVariable<unsigned int>(
+          "num_slots", boot_control_->GetNumSlots()));
 
   return true;
 }
diff --git a/update_manager/real_system_provider.h b/update_manager/real_system_provider.h
index 3fc2d8d..a46a698 100644
--- a/update_manager/real_system_provider.h
+++ b/update_manager/real_system_provider.h
@@ -20,6 +20,7 @@
 #include <memory>
 #include <string>
 
+#include "update_engine/boot_control_interface.h"
 #include "update_engine/hardware_interface.h"
 #include "update_engine/update_manager/system_provider.h"
 
@@ -29,8 +30,9 @@
 class RealSystemProvider : public SystemProvider {
  public:
   explicit RealSystemProvider(
-      chromeos_update_engine::HardwareInterface* hardware)
-      : hardware_(hardware) {}
+      chromeos_update_engine::HardwareInterface* hardware,
+      chromeos_update_engine::BootControlInterface* boot_control)
+      : hardware_(hardware), boot_control_(boot_control) {}
 
   // Initializes the provider and returns whether it succeeded.
   bool Init();
@@ -47,17 +49,18 @@
     return var_is_oobe_complete_.get();
   }
 
-  Variable<bool>* var_is_boot_device_removable() override {
-    return var_is_boot_device_removable_.get();
+  Variable<unsigned int>* var_num_slots() override {
+    return var_num_slots_.get();
   }
 
  private:
   std::unique_ptr<Variable<bool>> var_is_normal_boot_mode_;
   std::unique_ptr<Variable<bool>> var_is_official_build_;
   std::unique_ptr<Variable<bool>> var_is_oobe_complete_;
-  std::unique_ptr<Variable<bool>> var_is_boot_device_removable_;
+  std::unique_ptr<Variable<unsigned int>> var_num_slots_;
 
   chromeos_update_engine::HardwareInterface* hardware_;
+  chromeos_update_engine::BootControlInterface* boot_control_;
 
   DISALLOW_COPY_AND_ASSIGN(RealSystemProvider);
 };
diff --git a/update_manager/real_system_provider_unittest.cc b/update_manager/real_system_provider_unittest.cc
index e28cc5c..35e9be1 100644
--- a/update_manager/real_system_provider_unittest.cc
+++ b/update_manager/real_system_provider_unittest.cc
@@ -21,6 +21,7 @@
 #include <base/time/time.h>
 #include <gtest/gtest.h>
 
+#include "update_engine/fake_boot_control.h"
 #include "update_engine/fake_hardware.h"
 #include "update_engine/update_manager/umtest_utils.h"
 
@@ -31,11 +32,13 @@
 class UmRealSystemProviderTest : public ::testing::Test {
  protected:
   void SetUp() override {
-    provider_.reset(new RealSystemProvider(&fake_hardware_));
+    provider_.reset(
+        new RealSystemProvider(&fake_hardware_, &fake_boot_control_));
     EXPECT_TRUE(provider_->Init());
   }
 
   chromeos_update_engine::FakeHardware fake_hardware_;
+  chromeos_update_engine::FakeBootControl fake_boot_control_;
   unique_ptr<RealSystemProvider> provider_;
 };
 
diff --git a/update_manager/state_factory.cc b/update_manager/state_factory.cc
index 9bd028a..f90bd6e 100644
--- a/update_manager/state_factory.cc
+++ b/update_manager/state_factory.cc
@@ -48,7 +48,8 @@
   unique_ptr<RealShillProvider> shill_provider(
       new RealShillProvider(shill_proxy, clock));
   unique_ptr<RealSystemProvider> system_provider(
-      new RealSystemProvider(system_state->hardware()));
+      new RealSystemProvider(system_state->hardware(),
+                             system_state->boot_control()));
   unique_ptr<RealTimeProvider> time_provider(new RealTimeProvider(clock));
   unique_ptr<RealUpdaterProvider> updater_provider(
       new RealUpdaterProvider(system_state));
diff --git a/update_manager/system_provider.h b/update_manager/system_provider.h
index 5edec18..00fb9af 100644
--- a/update_manager/system_provider.h
+++ b/update_manager/system_provider.h
@@ -39,8 +39,8 @@
   // Returns a variable that tells whether OOBE was completed.
   virtual Variable<bool>* var_is_oobe_complete() = 0;
 
-  // Returns a variable that tells the boot device is removable (USB stick etc).
-  virtual Variable<bool>* var_is_boot_device_removable() = 0;
+  // Returns a variable that tells the number of slots in the system.
+  virtual Variable<unsigned int>* var_num_slots() = 0;
 
  protected:
   SystemProvider() {}
diff --git a/utils.cc b/utils.cc
index ebc849e..a50e56a 100644
--- a/utils.cc
+++ b/utils.cc
@@ -155,25 +155,6 @@
   return "";
 }
 
-
-const string KernelDeviceOfBootDevice(const string& boot_device) {
-  string kernel_partition_name;
-
-  string disk_name;
-  int partition_num;
-  if (SplitPartitionName(boot_device, &disk_name, &partition_num)) {
-    // Currently this assumes the partition number of the boot device is
-    // 3, 5, or 7, and changes it to 2, 4, or 6, respectively, to
-    // get the kernel device.
-    if (partition_num == 3 || partition_num == 5 || partition_num == 7) {
-      kernel_partition_name = MakePartitionName(disk_name, partition_num - 1);
-    }
-  }
-
-  return kernel_partition_name;
-}
-
-
 bool WriteFile(const char* path, const void* data, int data_len) {
   DirectFileWriter writer;
   TEST_AND_RETURN_FALSE_ERRNO(0 == writer.Open(path,
@@ -438,18 +419,6 @@
   }
 }
 
-string GetDiskName(const string& partition_name) {
-  string disk_name;
-  return SplitPartitionName(partition_name, &disk_name, nullptr) ?
-      disk_name : string();
-}
-
-int GetPartitionNumber(const string& partition_name) {
-  int partition_num = 0;
-  return SplitPartitionName(partition_name, nullptr, &partition_num) ?
-      partition_num : 0;
-}
-
 bool SplitPartitionName(const string& partition_name,
                         string* out_disk_name,
                         int* out_partition_num) {
@@ -543,26 +512,6 @@
   return part_name;
 }
 
-string SysfsBlockDevice(const string& device) {
-  base::FilePath device_path(device);
-  if (device_path.DirName().value() != "/dev") {
-    return "";
-  }
-  return base::FilePath("/sys/block").Append(device_path.BaseName()).value();
-}
-
-bool IsRemovableDevice(const string& device) {
-  string sysfs_block = SysfsBlockDevice(device);
-  string removable;
-  if (sysfs_block.empty() ||
-      !base::ReadFileToString(base::FilePath(sysfs_block).Append("removable"),
-                              &removable)) {
-    return false;
-  }
-  base::TrimWhitespaceASCII(removable, base::TRIM_ALL, &removable);
-  return removable == "1";
-}
-
 string ErrnoNumberAsString(int err) {
   char buf[100];
   buf[0] = '\0';
@@ -1488,27 +1437,6 @@
   return result;
 }
 
-bool GetInstallDev(const string& boot_dev, string* install_dev) {
-  string disk_name;
-  int partition_num;
-  if (!SplitPartitionName(boot_dev, &disk_name, &partition_num))
-    return false;
-
-  // Right now, we just switch '3' and '5' partition numbers.
-  if (partition_num == 3) {
-    partition_num = 5;
-  } else if (partition_num == 5) {
-    partition_num = 3;
-  } else {
-    return false;
-  }
-
-  if (install_dev)
-    *install_dev = MakePartitionName(disk_name, partition_num);
-
-  return true;
-}
-
 Time TimeFromStructTimespec(struct timespec *ts) {
   int64_t us = static_cast<int64_t>(ts->tv_sec) * Time::kMicrosecondsPerSecond +
       static_cast<int64_t>(ts->tv_nsec) / Time::kNanosecondsPerMicrosecond;
diff --git a/utils.h b/utils.h
index b5c3fec..b165562 100644
--- a/utils.h
+++ b/utils.h
@@ -65,11 +65,6 @@
 // "mosys" command.
 std::string ParseECVersion(std::string input_line);
 
-// Given the name of the block device of a boot partition, return the
-// name of the associated kernel partition (e.g. given "/dev/sda3",
-// return "/dev/sda2").
-const std::string KernelDeviceOfBootDevice(const std::string& boot_device);
-
 // 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 char* path, const void* data, int data_len);
@@ -155,16 +150,6 @@
 bool MakeTempDirectory(const std::string& base_dirname_template,
                        std::string* dirname);
 
-// Returns the disk device name for a partition. For example,
-// GetDiskName("/dev/sda3") returns "/dev/sda". Returns an empty string
-// if the input device is not of the "/dev/xyz#" form.
-std::string GetDiskName(const std::string& partition_name);
-
-// Returns the partition number, of partition device name. For example,
-// GetPartitionNumber("/dev/sda3") returns 3.
-// Returns 0 on failure
-int GetPartitionNumber(const std::string& partition_name);
-
 // Splits the partition device name into the block device name and partition
 // number. For example, "/dev/sda3" will be split into {"/dev/sda", 3} and
 // "/dev/mmcblk0p2" into {"/dev/mmcblk0", 2}
@@ -193,16 +178,6 @@
 // /dev/sda3. Return empty string on error.
 std::string MakePartitionNameForMount(const std::string& part_name);
 
-// Returns the sysfs block device for a root block device. For
-// example, SysfsBlockDevice("/dev/sda") returns
-// "/sys/block/sda". Returns an empty string if the input device is
-// not of the "/dev/xyz" form.
-std::string SysfsBlockDevice(const std::string& device);
-
-// Returns true if the root |device| (e.g., "/dev/sdb") is known to be
-// removable, false otherwise.
-bool IsRemovableDevice(const std::string& device);
-
 // Synchronously mount or unmount a filesystem. Return true on success.
 // When mounting, it will attempt to mount the the device as "ext3", "ext2" and
 // "squashfs", with the passed |flags| options.
@@ -380,13 +355,6 @@
 // global default. Returns true if successfully deleted. False otherwise.
 bool DeletePowerwashMarkerFile(const char* file_path);
 
-// Assumes you want to install on the "other" device, where the other
-// device is what you get if you swap 1 for 2 or 3 for 4 or vice versa
-// for the number at the end of the boot device. E.g., /dev/sda1 -> /dev/sda2
-// or /dev/sda4 -> /dev/sda3. See
-// http://www.chromium.org/chromium-os/chromiumos-design-docs/disk-format
-bool GetInstallDev(const std::string& boot_dev, std::string* install_dev);
-
 // Decodes the data in |base64_encoded| and stores it in a temporary
 // file. Returns false if the given data is empty, not well-formed
 // base64 or if an error occurred. If true is returned, the decoded
diff --git a/utils_unittest.cc b/utils_unittest.cc
index 41ea036..6842b21 100644
--- a/utils_unittest.cc
+++ b/utils_unittest.cc
@@ -62,37 +62,6 @@
   EXPECT_EQ("", utils::ParseECVersion("b=1231a fw_version a=fasd2"));
 }
 
-
-TEST(UtilsTest, KernelDeviceOfBootDevice) {
-  EXPECT_EQ("", utils::KernelDeviceOfBootDevice(""));
-  EXPECT_EQ("", utils::KernelDeviceOfBootDevice("foo"));
-  EXPECT_EQ("", utils::KernelDeviceOfBootDevice("/dev/sda0"));
-  EXPECT_EQ("", utils::KernelDeviceOfBootDevice("/dev/sda1"));
-  EXPECT_EQ("", utils::KernelDeviceOfBootDevice("/dev/sda2"));
-  EXPECT_EQ("/dev/sda2", utils::KernelDeviceOfBootDevice("/dev/sda3"));
-  EXPECT_EQ("", utils::KernelDeviceOfBootDevice("/dev/sda4"));
-  EXPECT_EQ("/dev/sda4", utils::KernelDeviceOfBootDevice("/dev/sda5"));
-  EXPECT_EQ("", utils::KernelDeviceOfBootDevice("/dev/sda6"));
-  EXPECT_EQ("/dev/sda6", utils::KernelDeviceOfBootDevice("/dev/sda7"));
-  EXPECT_EQ("", utils::KernelDeviceOfBootDevice("/dev/sda8"));
-  EXPECT_EQ("", utils::KernelDeviceOfBootDevice("/dev/sda9"));
-
-  EXPECT_EQ("/dev/mmcblk0p2",
-            utils::KernelDeviceOfBootDevice("/dev/mmcblk0p3"));
-  EXPECT_EQ("", utils::KernelDeviceOfBootDevice("/dev/mmcblk0p4"));
-
-  EXPECT_EQ("/dev/mtd2", utils::KernelDeviceOfBootDevice("/dev/ubi3"));
-  EXPECT_EQ("", utils::KernelDeviceOfBootDevice("/dev/ubi4"));
-
-  EXPECT_EQ("/dev/mtd2",
-            utils::KernelDeviceOfBootDevice("/dev/ubiblock3_0"));
-  EXPECT_EQ("/dev/mtd4",
-            utils::KernelDeviceOfBootDevice("/dev/ubiblock5_0"));
-  EXPECT_EQ("/dev/mtd6",
-            utils::KernelDeviceOfBootDevice("/dev/ubiblock7_0"));
-  EXPECT_EQ("", utils::KernelDeviceOfBootDevice("/dev/ubiblock4_0"));
-}
-
 TEST(UtilsTest, ReadFileFailure) {
   chromeos::Blob empty;
   EXPECT_FALSE(utils::ReadFile("/this/doesn't/exist", &empty));
@@ -137,47 +106,47 @@
   EXPECT_TRUE(test_utils::RecursiveUnlinkDir(temp_dir));
 }
 
-TEST(UtilsTest, GetDiskNameTest) {
-  EXPECT_EQ("/dev/sda", utils::GetDiskName("/dev/sda3"));
-  EXPECT_EQ("/dev/sdp", utils::GetDiskName("/dev/sdp1234"));
-  EXPECT_EQ("/dev/mmcblk0", utils::GetDiskName("/dev/mmcblk0p3"));
-  EXPECT_EQ("", utils::GetDiskName("/dev/mmcblk0p"));
-  EXPECT_EQ("", utils::GetDiskName("/dev/sda"));
-  EXPECT_EQ("/dev/ubiblock", utils::GetDiskName("/dev/ubiblock3_2"));
-  EXPECT_EQ("", utils::GetDiskName("/dev/foo/bar"));
-  EXPECT_EQ("", utils::GetDiskName("/"));
-  EXPECT_EQ("", utils::GetDiskName(""));
-}
+TEST(UtilsTest, SplitPartitionNameTest) {
+  string disk;
+  int part_num;
 
-TEST(UtilsTest, SysfsBlockDeviceTest) {
-  EXPECT_EQ("/sys/block/sda", utils::SysfsBlockDevice("/dev/sda"));
-  EXPECT_EQ("", utils::SysfsBlockDevice("/foo/sda"));
-  EXPECT_EQ("", utils::SysfsBlockDevice("/dev/foo/bar"));
-  EXPECT_EQ("", utils::SysfsBlockDevice("/"));
-  EXPECT_EQ("", utils::SysfsBlockDevice("./"));
-  EXPECT_EQ("", utils::SysfsBlockDevice(""));
-}
+  EXPECT_TRUE(utils::SplitPartitionName("/dev/sda3", &disk, &part_num));
+  EXPECT_EQ("/dev/sda", disk);
+  EXPECT_EQ(3, part_num);
 
-TEST(UtilsTest, IsRemovableDeviceTest) {
-  EXPECT_FALSE(utils::IsRemovableDevice(""));
-  EXPECT_FALSE(utils::IsRemovableDevice("/dev/non-existent-device"));
-}
+  EXPECT_TRUE(utils::SplitPartitionName("/dev/sdp1234", &disk, &part_num));
+  EXPECT_EQ("/dev/sdp", disk);
+  EXPECT_EQ(1234, part_num);
 
-TEST(UtilsTest, GetPartitionNumberTest) {
-  EXPECT_EQ(3, utils::GetPartitionNumber("/dev/sda3"));
-  EXPECT_EQ(3, utils::GetPartitionNumber("/dev/sdz3"));
-  EXPECT_EQ(123, utils::GetPartitionNumber("/dev/sda123"));
-  EXPECT_EQ(2, utils::GetPartitionNumber("/dev/mmcblk0p2"));
-  EXPECT_EQ(0, utils::GetPartitionNumber("/dev/mmcblk0p"));
-  EXPECT_EQ(3, utils::GetPartitionNumber("/dev/ubiblock3_2"));
-  EXPECT_EQ(0, utils::GetPartitionNumber(""));
-  EXPECT_EQ(0, utils::GetPartitionNumber("/"));
-  EXPECT_EQ(0, utils::GetPartitionNumber("/dev/"));
-  EXPECT_EQ(0, utils::GetPartitionNumber("/dev/sda"));
-  EXPECT_EQ(10, utils::GetPartitionNumber("/dev/loop10"));
-  EXPECT_EQ(11, utils::GetPartitionNumber("/dev/loop28p11"));
-  EXPECT_EQ(10, utils::GetPartitionNumber("/dev/loop10_0"));
-  EXPECT_EQ(11, utils::GetPartitionNumber("/dev/loop28p11_0"));
+  EXPECT_TRUE(utils::SplitPartitionName("/dev/mmcblk0p3", &disk, &part_num));
+  EXPECT_EQ("/dev/mmcblk0", disk);
+  EXPECT_EQ(3, part_num);
+
+  EXPECT_TRUE(utils::SplitPartitionName("/dev/ubiblock3_2", &disk, &part_num));
+  EXPECT_EQ("/dev/ubiblock", disk);
+  EXPECT_EQ(3, part_num);
+
+  EXPECT_TRUE(utils::SplitPartitionName("/dev/loop10", &disk, &part_num));
+  EXPECT_EQ("/dev/loop", disk);
+  EXPECT_EQ(10, part_num);
+
+  EXPECT_TRUE(utils::SplitPartitionName("/dev/loop28p11", &disk, &part_num));
+  EXPECT_EQ("/dev/loop28", disk);
+  EXPECT_EQ(11, part_num);
+
+  EXPECT_TRUE(utils::SplitPartitionName("/dev/loop10_0", &disk, &part_num));
+  EXPECT_EQ("/dev/loop", disk);
+  EXPECT_EQ(10, part_num);
+
+  EXPECT_TRUE(utils::SplitPartitionName("/dev/loop28p11_0", &disk, &part_num));
+  EXPECT_EQ("/dev/loop28", disk);
+  EXPECT_EQ(11, part_num);
+
+  EXPECT_FALSE(utils::SplitPartitionName("/dev/mmcblk0p", &disk, &part_num));
+  EXPECT_FALSE(utils::SplitPartitionName("/dev/sda", &disk, &part_num));
+  EXPECT_FALSE(utils::SplitPartitionName("/dev/foo/bar", &disk, &part_num));
+  EXPECT_FALSE(utils::SplitPartitionName("/", &disk, &part_num));
+  EXPECT_FALSE(utils::SplitPartitionName("", &disk, &part_num));
 }
 
 TEST(UtilsTest, MakePartitionNameTest) {
@@ -333,31 +302,6 @@
   EXPECT_EQ(6, block_count);
 }
 
-TEST(UtilsTest, GetInstallDevTest) {
-  string boot_dev = "/dev/sda5";
-  string install_dev;
-  EXPECT_TRUE(utils::GetInstallDev(boot_dev, &install_dev));
-  EXPECT_EQ(install_dev, "/dev/sda3");
-
-  boot_dev = "/dev/sda3";
-  EXPECT_TRUE(utils::GetInstallDev(boot_dev, &install_dev));
-  EXPECT_EQ(install_dev, "/dev/sda5");
-
-  boot_dev = "/dev/sda12";
-  EXPECT_FALSE(utils::GetInstallDev(boot_dev, &install_dev));
-
-  boot_dev = "/dev/ubiblock3_0";
-  EXPECT_TRUE(utils::GetInstallDev(boot_dev, &install_dev));
-  EXPECT_EQ(install_dev, "/dev/ubi5_0");
-
-  boot_dev = "/dev/ubiblock5_0";
-  EXPECT_TRUE(utils::GetInstallDev(boot_dev, &install_dev));
-  EXPECT_EQ(install_dev, "/dev/ubi3_0");
-
-  boot_dev = "/dev/ubiblock12_0";
-  EXPECT_FALSE(utils::GetInstallDev(boot_dev, &install_dev));
-}
-
 namespace {
 void GetFileFormatTester(const string& expected,
                          const vector<uint8_t>& contents) {