Implement powerwash on Android.

Powerwash, the name for the equivalent of a factory reset or /data wipe,
can be triggered in Android by writing the desired command to the
recovery command file and rebooting into recovery.

This patch moves the powerwash scheduling/canceling logic to the
HardwareInterface and implements it on Android.

Bug: 28700985
TEST=Called update_engine_client passing POWERWASH=1, BCB is stored up
to offset 832.

Change-Id: If737fd4b9b3e2ed9bce709b3b59f22e9f0a3dc9a
diff --git a/Android.mk b/Android.mk
index 97b9f7a..141b144 100644
--- a/Android.mk
+++ b/Android.mk
@@ -288,7 +288,8 @@
 LOCAL_LDFLAGS := $(ue_common_ldflags)
 LOCAL_C_INCLUDES := \
     $(ue_common_c_includes) \
-    $(ue_libupdate_engine_exported_c_includes)
+    $(ue_libupdate_engine_exported_c_includes) \
+    bootable/recovery
 LOCAL_STATIC_LIBRARIES := \
     libpayload_consumer \
     update_metadata-protos \
@@ -340,6 +341,7 @@
     update_manager/state_factory.cc \
     update_manager/update_manager.cc \
     update_status_utils.cc \
+    utils_android.cc \
     weave_service_factory.cc
 ifeq ($(local_use_binder),1)
 LOCAL_AIDL_INCLUDES += $(LOCAL_PATH)/binder_bindings
@@ -390,7 +392,9 @@
 LOCAL_CFLAGS := $(ue_common_cflags)
 LOCAL_CPPFLAGS := $(ue_common_cppflags)
 LOCAL_LDFLAGS := $(ue_common_ldflags)
-LOCAL_C_INCLUDES :=  $(ue_common_c_includes)
+LOCAL_C_INCLUDES := \
+    $(ue_common_c_includes) \
+    bootable/recovery
 #TODO(deymo): Remove external/cros/system_api/dbus once the strings are moved
 # out of the DBus interface.
 LOCAL_C_INCLUDES += \
@@ -411,7 +415,8 @@
     hardware_android.cc \
     proxy_resolver.cc \
     update_attempter_android.cc \
-    update_status_utils.cc
+    update_status_utils.cc \
+    utils_android.cc
 include $(BUILD_STATIC_LIBRARY)
 
 endif  # local_use_omaha == 1
diff --git a/boot_control_android.cc b/boot_control_android.cc
index 78df37f..b3b7630 100644
--- a/boot_control_android.cc
+++ b/boot_control_android.cc
@@ -22,34 +22,12 @@
 #include <base/strings/string_util.h>
 #include <brillo/make_unique_ptr.h>
 #include <brillo/message_loops/message_loop.h>
-#include <cutils/properties.h>
-#include <fs_mgr.h>
 
 #include "update_engine/common/utils.h"
+#include "update_engine/utils_android.h"
 
 using std::string;
 
-namespace {
-
-// Open the appropriate fstab file and fallback to /fstab.device if
-// that's what's being used.
-static struct fstab* OpenFSTab() {
-  char propbuf[PROPERTY_VALUE_MAX];
-  struct fstab* fstab;
-
-  property_get("ro.hardware", propbuf, "");
-  string fstab_name = string("/fstab.") + propbuf;
-  fstab = fs_mgr_read_fstab(fstab_name.c_str());
-  if (fstab != nullptr)
-    return fstab;
-
-  fstab = fs_mgr_read_fstab("/fstab.device");
-  return fstab;
-}
-
-}  // namespace
-
-
 namespace chromeos_update_engine {
 
 namespace boot_control {
@@ -97,9 +75,6 @@
 bool BootControlAndroid::GetPartitionDevice(const string& partition_name,
                                             Slot slot,
                                             string* device) const {
-  struct fstab* fstab;
-  struct fstab_rec* record;
-
   // We can't use fs_mgr to look up |partition_name| because fstab
   // doesn't list every slot partition (it uses the slotselect option
   // to mask the suffix).
@@ -119,20 +94,9 @@
   // of misc and then finding an entry in /dev matching the sysfs
   // entry.
 
-  fstab = OpenFSTab();
-  if (fstab == nullptr) {
-    LOG(ERROR) << "Error opening fstab file.";
+  base::FilePath misc_device;
+  if (!utils::DeviceForMountPoint("/misc", &misc_device))
     return false;
-  }
-  record = fs_mgr_get_entry_for_mount_point(fstab, "/misc");
-  if (record == nullptr) {
-    LOG(ERROR) << "Error finding /misc entry in fstab file.";
-    fs_mgr_free_fstab(fstab);
-    return false;
-  }
-
-  base::FilePath misc_device = base::FilePath(record->blk_device);
-  fs_mgr_free_fstab(fstab);
 
   if (!utils::IsSymlink(misc_device.value().c_str())) {
     LOG(ERROR) << "Device file " << misc_device.value() << " for /misc "
diff --git a/common/constants.cc b/common/constants.cc
index c0bb874..0ac22f3 100644
--- a/common/constants.cc
+++ b/common/constants.cc
@@ -18,11 +18,6 @@
 
 namespace chromeos_update_engine {
 
-const char kPowerwashMarkerFile[] =
-    "/mnt/stateful_partition/factory_install_reset";
-
-const char kPowerwashCommand[] = "safe fast keepimg reason=update_engine\n";
-
 const char kPowerwashSafePrefsSubDirectory[] = "update_engine/prefs";
 
 const char kPrefsSubDirectory[] = "prefs";
@@ -97,5 +92,6 @@
 const char kPayloadPropertyMetadataHash[] = "METADATA_HASH";
 const char kPayloadPropertyAuthorization[] = "AUTHORIZATION";
 const char kPayloadPropertyUserAgent[] = "USER_AGENT";
+const char kPayloadPropertyPowerwash[] = "POWERWASH";
 
 }  // namespace chromeos_update_engine
diff --git a/common/constants.h b/common/constants.h
index 660d9a9..649034e 100644
--- a/common/constants.h
+++ b/common/constants.h
@@ -19,13 +19,6 @@
 
 namespace chromeos_update_engine {
 
-// The name of the marker file used to trigger powerwash when post-install
-// completes successfully so that the device is powerwashed on next reboot.
-extern const char kPowerwashMarkerFile[];
-
-// The contents of the powerwash marker file.
-extern const char kPowerwashCommand[];
-
 // Directory for AU prefs that are preserved across powerwash.
 extern const char kPowerwashSafePrefsSubDirectory[];
 
@@ -100,6 +93,7 @@
 extern const char kPayloadPropertyMetadataHash[];
 extern const char kPayloadPropertyAuthorization[];
 extern const char kPayloadPropertyUserAgent[];
+extern const char kPayloadPropertyPowerwash[];
 
 // A download source is any combination of protocol and server (that's of
 // interest to us when looking at UMA metrics) using which we may download
diff --git a/common/fake_hardware.h b/common/fake_hardware.h
index 4558c8c..2da12ad 100644
--- a/common/fake_hardware.h
+++ b/common/fake_hardware.h
@@ -57,6 +57,18 @@
 
   int GetPowerwashCount() const override { return powerwash_count_; }
 
+  bool SchedulePowerwash() override {
+    powerwash_scheduled_ = true;
+    return true;
+  }
+
+  bool CancelPowerwash() override {
+    powerwash_scheduled_ = false;
+    return true;
+  }
+
+  bool IsPowerwashScheduled() { return powerwash_scheduled_; }
+
   bool GetNonVolatileDirectory(base::FilePath* path) const override {
     return false;
   }
@@ -115,6 +127,7 @@
   std::string firmware_version_{"Fake Firmware v1.0.1"};
   std::string ec_version_{"Fake EC v1.0a"};
   int powerwash_count_{kPowerwashCountNotSet};
+  bool powerwash_scheduled_{false};
 
   DISALLOW_COPY_AND_ASSIGN(FakeHardware);
 };
diff --git a/common/hardware_interface.h b/common/hardware_interface.h
index e434cc9..f5f900e 100644
--- a/common/hardware_interface.h
+++ b/common/hardware_interface.h
@@ -70,6 +70,13 @@
   // recovery don't have this value set.
   virtual int GetPowerwashCount() const = 0;
 
+  // Signals that a powerwash (stateful partition wipe) should be performed
+  // after reboot.
+  virtual bool SchedulePowerwash() = 0;
+
+  // Cancel the powerwash operation scheduled to be performed on next boot.
+  virtual bool CancelPowerwash() = 0;
+
   // Store in |path| the path to a non-volatile directory (persisted across
   // reboots) available for this daemon. In case of an error, such as no
   // directory available, returns false.
diff --git a/common/utils.cc b/common/utils.cc
index f7d4585..a352961 100644
--- a/common/utils.cc
+++ b/common/utils.cc
@@ -879,35 +879,6 @@
   return base_code;
 }
 
-bool CreatePowerwashMarkerFile(const char* file_path) {
-  const char* marker_file = file_path ? file_path : kPowerwashMarkerFile;
-  bool result = utils::WriteFile(marker_file,
-                                 kPowerwashCommand,
-                                 strlen(kPowerwashCommand));
-  if (result) {
-    LOG(INFO) << "Created " << marker_file << " to powerwash on next reboot";
-  } else {
-    PLOG(ERROR) << "Error in creating powerwash marker file: " << marker_file;
-  }
-
-  return result;
-}
-
-bool DeletePowerwashMarkerFile(const char* file_path) {
-  const char* marker_file = file_path ? file_path : kPowerwashMarkerFile;
-  const base::FilePath kPowerwashMarkerPath(marker_file);
-  bool result = base::DeleteFile(kPowerwashMarkerPath, false);
-
-  if (result)
-    LOG(INFO) << "Successfully deleted the powerwash marker file : "
-              << marker_file;
-  else
-    PLOG(ERROR) << "Could not delete the powerwash marker file : "
-                << marker_file;
-
-  return result;
-}
-
 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/common/utils.h b/common/utils.h
index e950b15..3987484 100644
--- a/common/utils.h
+++ b/common/utils.h
@@ -265,16 +265,6 @@
 // it'll return the same value again.
 ErrorCode GetBaseErrorCode(ErrorCode code);
 
-// Creates the powerwash marker file with the appropriate commands in it.  Uses
-// |file_path| as the path to the marker file if non-null, otherwise uses the
-// global default. Returns true if successfully created.  False otherwise.
-bool CreatePowerwashMarkerFile(const char* file_path);
-
-// Deletes the marker file used to trigger Powerwash using clobber-state.  Uses
-// |file_path| as the path to the marker file if non-null, otherwise uses the
-// global default. Returns true if successfully deleted. False otherwise.
-bool DeletePowerwashMarkerFile(const char* file_path);
-
 // 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/hardware_android.cc b/hardware_android.cc
index 60e26f2..8de02c7 100644
--- a/hardware_android.cc
+++ b/hardware_android.cc
@@ -16,17 +16,71 @@
 
 #include "update_engine/hardware_android.h"
 
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include <algorithm>
+
+#include <bootloader.h>
+
 #include <base/files/file_util.h>
 #include <brillo/make_unique_ptr.h>
 #include <cutils/properties.h>
 
 #include "update_engine/common/hardware.h"
 #include "update_engine/common/platform_constants.h"
+#include "update_engine/common/utils.h"
+#include "update_engine/utils_android.h"
 
 using std::string;
 
 namespace chromeos_update_engine {
 
+namespace {
+
+// The powerwash arguments passed to recovery. Arguments are separated by \n.
+const char kAndroidRecoveryPowerwashCommand[] =
+    "recovery\n"
+    "--wipe_data\n"
+    "--reason=wipe_data_from_ota\n";
+
+// Write a recovery command line |message| to the BCB. The arguments to recovery
+// must be separated by '\n'. An empty string will erase the BCB.
+bool WriteBootloaderRecoveryMessage(const string& message) {
+  base::FilePath misc_device;
+  if (!utils::DeviceForMountPoint("/misc", &misc_device))
+    return false;
+
+  // Setup a bootloader_message with just the command and recovery fields set.
+  bootloader_message boot = {};
+  if (!message.empty()) {
+    strncpy(boot.command, "boot-recovery", sizeof(boot.command) - 1);
+    memcpy(boot.recovery,
+           message.data(),
+           std::min(message.size(), sizeof(boot.recovery) - 1));
+  }
+
+  int fd =
+      HANDLE_EINTR(open(misc_device.value().c_str(), O_WRONLY | O_SYNC, 0600));
+  if (fd < 0) {
+    PLOG(ERROR) << "Opening misc";
+    return false;
+  }
+  ScopedFdCloser fd_closer(&fd);
+  // We only re-write the first part of the bootloader_message, up to and
+  // including the recovery message.
+  size_t boot_size =
+      offsetof(bootloader_message, recovery) + sizeof(boot.recovery);
+  if (!utils::WriteAll(fd, &boot, boot_size)) {
+    PLOG(ERROR) << "Writing recovery command to misc";
+    return false;
+  }
+  return true;
+}
+
+}  // namespace
+
 namespace hardware {
 
 // Factory defined in hardware.h.
@@ -100,6 +154,15 @@
   return 0;
 }
 
+bool HardwareAndroid::SchedulePowerwash() {
+  LOG(INFO) << "Scheduling a powerwash to BCB.";
+  return WriteBootloaderRecoveryMessage(kAndroidRecoveryPowerwashCommand);
+}
+
+bool HardwareAndroid::CancelPowerwash() {
+  return WriteBootloaderRecoveryMessage("");
+}
+
 bool HardwareAndroid::GetNonVolatileDirectory(base::FilePath* path) const {
   base::FilePath local_path(constants::kNonVolatileDirectory);
   if (!base::PathExists(local_path)) {
diff --git a/hardware_android.h b/hardware_android.h
index 9aa729c..88e00fa 100644
--- a/hardware_android.h
+++ b/hardware_android.h
@@ -42,6 +42,8 @@
   std::string GetFirmwareVersion() const override;
   std::string GetECVersion() const override;
   int GetPowerwashCount() const override;
+  bool SchedulePowerwash() override;
+  bool CancelPowerwash() override;
   bool GetNonVolatileDirectory(base::FilePath* path) const override;
   bool GetPowerwashSafeDirectory(base::FilePath* path) const override;
 
diff --git a/hardware_chromeos.cc b/hardware_chromeos.cc
index 57583a1..9b3a05d 100644
--- a/hardware_chromeos.cc
+++ b/hardware_chromeos.cc
@@ -53,6 +53,14 @@
 // a powerwash is performed.
 const char kPowerwashCountMarker[] = "powerwash_count";
 
+// The name of the marker file used to trigger powerwash when post-install
+// completes successfully so that the device is powerwashed on next reboot.
+const char kPowerwashMarkerFile[] =
+    "/mnt/stateful_partition/factory_install_reset";
+
+// The contents of the powerwash marker file.
+const char kPowerwashCommand[] = "safe fast keepimg reason=update_engine\n";
+
 // UpdateManager config path.
 const char* kConfigFilePath = "/etc/update_manager.conf";
 
@@ -162,6 +170,32 @@
   return powerwash_count;
 }
 
+bool HardwareChromeOS::SchedulePowerwash() {
+  bool result = utils::WriteFile(
+      kPowerwashMarkerFile, kPowerwashCommand, strlen(kPowerwashCommand));
+  if (result) {
+    LOG(INFO) << "Created " << marker_file << " to powerwash on next reboot";
+  } else {
+    PLOG(ERROR) << "Error in creating powerwash marker file: " << marker_file;
+  }
+
+  return result;
+}
+
+bool HardwareChromeOS::CancelPowerwash() {
+  bool result = base::DeleteFile(base::FilePath(kPowerwashMarkerFile), false);
+
+  if (result) {
+    LOG(INFO) << "Successfully deleted the powerwash marker file : "
+              << marker_file;
+  } else {
+    PLOG(ERROR) << "Could not delete the powerwash marker file : "
+                << marker_file;
+  }
+
+  return result;
+}
+
 bool HardwareChromeOS::GetNonVolatileDirectory(base::FilePath* path) const {
   *path = base::FilePath(constants::kNonVolatileDirectory);
   return true;
diff --git a/hardware_chromeos.h b/hardware_chromeos.h
index d9a73f8..bc0e891 100644
--- a/hardware_chromeos.h
+++ b/hardware_chromeos.h
@@ -45,6 +45,8 @@
   std::string GetFirmwareVersion() const override;
   std::string GetECVersion() const override;
   int GetPowerwashCount() const override;
+  bool SchedulePowerwash() override;
+  bool CancelPowerwash() override;
   bool GetNonVolatileDirectory(base::FilePath* path) const override;
   bool GetPowerwashSafeDirectory(base::FilePath* path) const override;
 
diff --git a/payload_consumer/postinstall_runner_action.cc b/payload_consumer/postinstall_runner_action.cc
index c24590e..47b1947 100644
--- a/payload_consumer/postinstall_runner_action.cc
+++ b/payload_consumer/postinstall_runner_action.cc
@@ -58,8 +58,8 @@
   install_plan_ = GetInputObject();
 
   if (install_plan_.powerwash_required) {
-    if (utils::CreatePowerwashMarkerFile(powerwash_marker_file_)) {
-      powerwash_marker_created_ = true;
+    if (hardware_->SchedulePowerwash()) {
+      powerwash_scheduled_ = true;
     } else {
       return CompletePostinstall(ErrorCode::kPostinstallPowerwashError);
     }
@@ -327,9 +327,9 @@
   if (error_code != ErrorCode::kSuccess) {
     LOG(ERROR) << "Postinstall action failed.";
 
-    // Undo any changes done to trigger Powerwash using clobber-state.
-    if (powerwash_marker_created_)
-      utils::DeletePowerwashMarkerFile(powerwash_marker_file_);
+    // Undo any changes done to trigger Powerwash.
+    if (powerwash_scheduled_)
+      hardware_->CancelPowerwash();
 
     return;
   }
diff --git a/payload_consumer/postinstall_runner_action.h b/payload_consumer/postinstall_runner_action.h
index 4cdc47e..2bde3ca 100644
--- a/payload_consumer/postinstall_runner_action.h
+++ b/payload_consumer/postinstall_runner_action.h
@@ -24,6 +24,8 @@
 #include <gtest/gtest_prod.h>
 
 #include "update_engine/common/action.h"
+#include "update_engine/common/boot_control_interface.h"
+#include "update_engine/common/hardware_interface.h"
 #include "update_engine/payload_consumer/install_plan.h"
 
 // The Postinstall Runner Action is responsible for running the postinstall
@@ -35,8 +37,9 @@
 
 class PostinstallRunnerAction : public InstallPlanAction {
  public:
-  explicit PostinstallRunnerAction(BootControlInterface* boot_control)
-      : PostinstallRunnerAction(boot_control, nullptr) {}
+  PostinstallRunnerAction(BootControlInterface* boot_control,
+                          HardwareInterface* hardware)
+      : boot_control_(boot_control), hardware_(hardware) {}
 
   // InstallPlanAction overrides.
   void PerformAction() override;
@@ -63,12 +66,6 @@
   friend class PostinstallRunnerActionTest;
   FRIEND_TEST(PostinstallRunnerActionTest, ProcessProgressLineTest);
 
-  // Special constructor used for testing purposes.
-  PostinstallRunnerAction(BootControlInterface* boot_control,
-                          const char* powerwash_marker_file)
-      : boot_control_(boot_control),
-        powerwash_marker_file_(powerwash_marker_file) {}
-
   void PerformPartitionPostinstall();
 
   // Called whenever the |progress_fd_| has data available to read.
@@ -127,13 +124,12 @@
   // The BootControlInerface used to mark the new slot as ready.
   BootControlInterface* boot_control_;
 
-  // True if Powerwash Marker was created before invoking post-install script.
-  // False otherwise. Used for cleaning up if post-install fails.
-  bool powerwash_marker_created_{false};
+  // HardwareInterface used to signal powerwash.
+  HardwareInterface* hardware_;
 
-  // Non-null value will cause post-install to override the default marker
-  // file name; used for testing.
-  const char* powerwash_marker_file_;
+  // Whether the Powerwash was scheduled before invoking post-install script.
+  // Used for cleaning up if post-install fails.
+  bool powerwash_scheduled_{false};
 
   // Postinstall command currently running, or 0 if no program running.
   pid_t current_command_{0};
diff --git a/payload_consumer/postinstall_runner_action_unittest.cc b/payload_consumer/postinstall_runner_action_unittest.cc
index 3b6b49a..5a8e950 100644
--- a/payload_consumer/postinstall_runner_action_unittest.cc
+++ b/payload_consumer/postinstall_runner_action_unittest.cc
@@ -26,7 +26,6 @@
 
 #include <base/bind.h>
 #include <base/files/file_util.h>
-#include <base/files/scoped_temp_dir.h>
 #include <base/message_loop/message_loop.h>
 #include <base/strings/string_util.h>
 #include <base/strings/stringprintf.h>
@@ -38,6 +37,7 @@
 
 #include "update_engine/common/constants.h"
 #include "update_engine/common/fake_boot_control.h"
+#include "update_engine/common/fake_hardware.h"
 #include "update_engine/common/test_utils.h"
 #include "update_engine/common/utils.h"
 
@@ -88,10 +88,6 @@
     loop_.SetAsCurrent();
     async_signal_handler_.Init();
     subprocess_.Init(&async_signal_handler_);
-    ASSERT_TRUE(working_dir_.CreateUniqueTempDir());
-    // We use a test-specific powerwash marker file, to avoid race conditions.
-    powerwash_marker_file_ =
-        working_dir_.path().Append("factory_install_reset").value();
     // These tests use the postinstall files generated by "generate_images.sh"
     // stored in the "disk_ext2_unittest.img" image.
     postinstall_image_ =
@@ -154,14 +150,11 @@
   brillo::AsynchronousSignalHandler async_signal_handler_;
   Subprocess subprocess_;
 
-  // A temporary working directory used for the test.
-  base::ScopedTempDir working_dir_;
-  string powerwash_marker_file_;
-
   // The path to the postinstall sample image.
   string postinstall_image_;
 
   FakeBootControl fake_boot_control_;
+  FakeHardware fake_hardware_;
   PostinstActionProcessorDelegate processor_delegate_;
 
   // The PostinstallRunnerAction delegate receiving the progress updates.
@@ -189,8 +182,7 @@
   install_plan.download_url = "http://127.0.0.1:8080/update";
   install_plan.powerwash_required = powerwash_required;
   feeder_action.set_obj(install_plan);
-  PostinstallRunnerAction runner_action(&fake_boot_control_,
-                                        powerwash_marker_file_.c_str());
+  PostinstallRunnerAction runner_action(&fake_boot_control_, &fake_hardware_);
   postinstall_action_ = &runner_action;
   runner_action.set_delegate(setup_action_delegate_);
   BondActions(&feeder_action, &runner_action);
@@ -216,8 +208,7 @@
 }
 
 TEST_F(PostinstallRunnerActionTest, ProcessProgressLineTest) {
-  PostinstallRunnerAction action(&fake_boot_control_,
-                                 powerwash_marker_file_.c_str());
+  PostinstallRunnerAction action(&fake_boot_control_, &fake_hardware_);
   testing::StrictMock<MockPostinstallRunnerActionDelegate> mock_delegate_;
   action.set_delegate(&mock_delegate_);
 
@@ -246,7 +237,7 @@
   EXPECT_TRUE(processor_delegate_.processing_done_called_);
 
   // Since powerwash_required was false, this should not trigger a powerwash.
-  EXPECT_FALSE(utils::FileExists(powerwash_marker_file_.c_str()));
+  EXPECT_FALSE(fake_hardware_.IsPowerwashScheduled());
 }
 
 TEST_F(PostinstallRunnerActionTest, RunAsRootRunSymlinkFileTest) {
@@ -261,11 +252,8 @@
   RunPosinstallAction(loop.dev(), "bin/postinst_example", true);
   EXPECT_EQ(ErrorCode::kSuccess, processor_delegate_.code_);
 
-  // Check that the powerwash marker file was set.
-  string actual_cmd;
-  EXPECT_TRUE(base::ReadFileToString(base::FilePath(powerwash_marker_file_),
-                                     &actual_cmd));
-  EXPECT_EQ(kPowerwashCommand, actual_cmd);
+  // Check that powerwash was scheduled.
+  EXPECT_TRUE(fake_hardware_.IsPowerwashScheduled());
 }
 
 // Runs postinstall from a partition file that doesn't mount, so it should
@@ -276,7 +264,7 @@
 
   // In case of failure, Postinstall should not signal a powerwash even if it
   // was requested.
-  EXPECT_FALSE(utils::FileExists(powerwash_marker_file_.c_str()));
+  EXPECT_FALSE(fake_hardware_.IsPowerwashScheduled());
 }
 
 // Check that the failures from the postinstall script cause the action to
diff --git a/update_attempter.cc b/update_attempter.cc
index 8f19f51..f9f12bc 100644
--- a/update_attempter.cc
+++ b/update_attempter.cc
@@ -587,7 +587,8 @@
 void UpdateAttempter::BuildPostInstallActions(
     InstallPlanAction* previous_action) {
   shared_ptr<PostinstallRunnerAction> postinstall_runner_action(
-      new PostinstallRunnerAction(system_state_->boot_control()));
+      new PostinstallRunnerAction(system_state_->boot_control(),
+                                  system_state_->hardware()));
   postinstall_runner_action->set_delegate(this);
   actions_.push_back(shared_ptr<AbstractAction>(postinstall_runner_action));
   BondActions(previous_action,
diff --git a/update_attempter_android.cc b/update_attempter_android.cc
index 87c7e0d..2e7fda6 100644
--- a/update_attempter_android.cc
+++ b/update_attempter_android.cc
@@ -168,7 +168,11 @@
 
   install_plan_.source_slot = boot_control_->GetCurrentSlot();
   install_plan_.target_slot = install_plan_.source_slot == 0 ? 1 : 0;
-  install_plan_.powerwash_required = false;
+
+  int data_wipe = 0;
+  install_plan_.powerwash_required =
+      base::StringToInt(headers[kPayloadPropertyPowerwash], &data_wipe) &&
+      data_wipe != 0;
 
   LOG(INFO) << "Using this install plan:";
   install_plan_.Dump();
@@ -403,7 +407,7 @@
       new FilesystemVerifierAction());
 
   shared_ptr<PostinstallRunnerAction> postinstall_runner_action(
-      new PostinstallRunnerAction(boot_control_));
+      new PostinstallRunnerAction(boot_control_, hardware_));
 
   download_action->set_delegate(this);
   download_action_ = download_action;
diff --git a/update_attempter_unittest.cc b/update_attempter_unittest.cc
index 99cd2c8..b9a60b3 100644
--- a/update_attempter_unittest.cc
+++ b/update_attempter_unittest.cc
@@ -282,7 +282,7 @@
             GetErrorCodeForAction(&filesystem_verifier_action,
                                   ErrorCode::kError));
   PostinstallRunnerAction postinstall_runner_action(
-      fake_system_state.fake_boot_control());
+      fake_system_state.fake_boot_control(), fake_system_state.fake_hardware());
   EXPECT_EQ(ErrorCode::kPostinstallRunnerError,
             GetErrorCodeForAction(&postinstall_runner_action,
                                   ErrorCode::kError));
diff --git a/utils_android.cc b/utils_android.cc
new file mode 100644
index 0000000..a4f1ea8
--- /dev/null
+++ b/utils_android.cc
@@ -0,0 +1,71 @@
+//
+// Copyright (C) 2016 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/utils_android.h"
+
+#include <cutils/properties.h>
+#include <fs_mgr.h>
+
+using std::string;
+
+namespace chromeos_update_engine {
+
+namespace {
+
+// Open the appropriate fstab file and fallback to /fstab.device if
+// that's what's being used.
+static struct fstab* OpenFSTab() {
+  char propbuf[PROPERTY_VALUE_MAX];
+  struct fstab* fstab;
+
+  property_get("ro.hardware", propbuf, "");
+  string fstab_name = string("/fstab.") + propbuf;
+  fstab = fs_mgr_read_fstab(fstab_name.c_str());
+  if (fstab != nullptr)
+    return fstab;
+
+  fstab = fs_mgr_read_fstab("/fstab.device");
+  return fstab;
+}
+
+}  // namespace
+
+namespace utils {
+
+bool DeviceForMountPoint(const string& mount_point, base::FilePath* device) {
+  struct fstab* fstab;
+  struct fstab_rec* record;
+
+  fstab = OpenFSTab();
+  if (fstab == nullptr) {
+    LOG(ERROR) << "Error opening fstab file.";
+    return false;
+  }
+  record = fs_mgr_get_entry_for_mount_point(fstab, mount_point.c_str());
+  if (record == nullptr) {
+    LOG(ERROR) << "Error finding " << mount_point << " entry in fstab file.";
+    fs_mgr_free_fstab(fstab);
+    return false;
+  }
+
+  *device = base::FilePath(record->blk_device);
+  fs_mgr_free_fstab(fstab);
+  return true;
+}
+
+}  // namespace utils
+
+}  // namespace chromeos_update_engine
diff --git a/utils_android.h b/utils_android.h
new file mode 100644
index 0000000..18dd8ab
--- /dev/null
+++ b/utils_android.h
@@ -0,0 +1,37 @@
+//
+// Copyright (C) 2016 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_UTILS_ANDROID_H_
+#define UPDATE_ENGINE_UTILS_ANDROID_H_
+
+#include <string>
+
+#include <base/files/file_util.h>
+
+namespace chromeos_update_engine {
+
+namespace utils {
+
+// Find the block device that should be mounted in the |mount_point| path and
+// store it in |device|. Returns whether a device was found on the fstab.
+bool DeviceForMountPoint(const std::string& mount_point,
+                         base::FilePath* device);
+
+}  // namespace utils
+
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_UTILS_ANDROID_H_