Merge "update_engine: New BootControlInterface class."
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..508f578f
--- /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) {