update_engine: Do not cancel external powerwash in invalidations
Do not cancel powerwash initiated with reason != update_engine during
update invalidations.
Also keep the powerwash marker file in case if there is an error when
fetching the powerwash reason.
BUG=None
TEST=cros_run_unit_tests --board=BOARD --packages update_engine
TEST=tast run -var=tlwAddress=<DUT> autoupdate.OmahaInvalidation.*
Change-Id: I6d1d5c5a68476643674bebf7d9a4f0fd9cde7ede
Reviewed-on: https://chromium-review.googlesource.com/c/aosp/platform/system/update_engine/+/4957016
Commit-Queue: Artyom Chen <artyomchen@google.com>
Reviewed-by: John Admanski <jadmanski@chromium.org>
Reviewed-by: Jae Hoon Kim <kimjae@chromium.org>
Tested-by: Artyom Chen <artyomchen@google.com>
diff --git a/common/fake_hardware.h b/common/fake_hardware.h
index 65a6897..64b7c11 100644
--- a/common/fake_hardware.h
+++ b/common/fake_hardware.h
@@ -19,6 +19,7 @@
#include <map>
#include <memory>
+#include <optional>
#include <string>
#include <utility>
@@ -299,6 +300,17 @@
}
bool IsFWTryNextSlotReset() const { return reset_fw_try_next_slot_; }
+ std::optional<bool> IsPowerwashScheduledByUpdateEngine() const override {
+ return is_powerwash_scheduled_by_update_engine_;
+ }
+ void SetIsPowerwashScheduledByUpdateEngine(std::optional<bool> value) {
+ is_powerwash_scheduled_by_update_engine_ = value;
+ }
+
+ base::FilePath GetPowerwashMarkerFullPath() const override {
+ return base::FilePath();
+ };
+
private:
bool is_official_build_{true};
bool is_normal_boot_mode_{true};
@@ -318,6 +330,7 @@
int kernel_max_rollforward_{kKernelMaxRollforward};
int firmware_max_rollforward_{kFirmwareMaxRollforward};
int powerwash_count_{kPowerwashCountNotSet};
+ std::optional<bool> is_powerwash_scheduled_by_update_engine_{true};
bool powerwash_scheduled_{false};
bool save_rollback_data_{false};
int64_t build_timestamp_{0};
diff --git a/common/hardware_interface.h b/common/hardware_interface.h
index 9d7be40..ebae384 100644
--- a/common/hardware_interface.h
+++ b/common/hardware_interface.h
@@ -20,6 +20,7 @@
#include <stdint.h>
#include <memory>
+#include <optional>
#include <string>
#include <vector>
@@ -115,6 +116,12 @@
// recovery don't have this value set.
virtual int GetPowerwashCount() const = 0;
+ // Parses the powerwash marker file and returns if powerwash is
+ // initiated by update engine. If the file is not found, returns
+ // std::nullopt. If parsing failed, returns false.
+ // Otherwise returns if a marker file contains reason=update_engine.
+ virtual std::optional<bool> IsPowerwashScheduledByUpdateEngine() const = 0;
+
// Signals that a powerwash (stateful partition wipe) should be performed
// after reboot. If |save_rollback_data| is true additional state is
// preserved during shutdown that can be restored after the powerwash.
@@ -199,6 +206,9 @@
// Resets a RW firmware partition slot to try on next boot to a current slot.
// Returns false on failure, true on success.
virtual bool ResetFWTryNextSlot() = 0;
+
+ // Returns an absolute path to a powerwash marker file.
+ virtual base::FilePath GetPowerwashMarkerFullPath() const = 0;
};
} // namespace chromeos_update_engine
diff --git a/cros/hardware_chromeos.cc b/cros/hardware_chromeos.cc
index c7f72d8..24f4bed 100644
--- a/cros/hardware_chromeos.cc
+++ b/cros/hardware_chromeos.cc
@@ -16,6 +16,7 @@
#include "update_engine/cros/hardware_chromeos.h"
+#include <optional>
#include <utility>
#include <base/files/file_path.h>
@@ -64,10 +65,14 @@
// a powerwash is performed.
const char kPowerwashCountMarker[] = "powerwash_count";
-// The name of the marker file used to trigger powerwash when post-install
+// The path 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";
+constexpr char kPowerwashMarkerPath[] =
+ "mnt/stateful_partition/factory_install_reset";
+
+// Expected tag in the powerwash marker file that indicates that
+// powerwash is initiated by the update engine.
+constexpr char kPowerwashReasonUpdateEngineTag[] = "reason=update_engine";
// The name of the marker file used to trigger a save of rollback data
// during the next shutdown.
@@ -326,29 +331,52 @@
}
auto powerwash_command = GeneratePowerwashCommand(save_rollback_data);
- bool result = utils::WriteFile(
- kPowerwashMarkerFile, powerwash_command.data(), powerwash_command.size());
+ const std::string powerwash_marker_full_path =
+ GetPowerwashMarkerFullPath().value();
+ bool result = utils::WriteFile(powerwash_marker_full_path.c_str(),
+ powerwash_command.data(),
+ powerwash_command.size());
if (result) {
- LOG(INFO) << "Created " << kPowerwashMarkerFile
+ LOG(INFO) << "Created " << powerwash_marker_full_path
<< " to powerwash on next reboot ("
<< "save_rollback_data=" << save_rollback_data << ")";
} else {
PLOG(ERROR) << "Error in creating powerwash marker file: "
- << kPowerwashMarkerFile;
+ << powerwash_marker_full_path;
}
return result;
}
+std::optional<bool> HardwareChromeOS::IsPowerwashScheduledByUpdateEngine()
+ const {
+ const std::string powerwash_marker_full_path =
+ GetPowerwashMarkerFullPath().value();
+
+ if (!utils::FileExists(powerwash_marker_full_path.c_str())) {
+ return std::nullopt;
+ }
+
+ std::string contents;
+ if (!utils::ReadFile(powerwash_marker_full_path, &contents)) {
+ LOG(ERROR) << "Failed to read the powerwash marker file.";
+ return false;
+ }
+
+ return contents.find(kPowerwashReasonUpdateEngineTag) != std::string::npos;
+}
+
bool HardwareChromeOS::CancelPowerwash() {
- bool result = base::DeleteFile(base::FilePath(kPowerwashMarkerFile));
+ const base::FilePath powerwash_marker_full_path =
+ GetPowerwashMarkerFullPath();
+ bool result = base::DeleteFile(powerwash_marker_full_path);
if (result) {
LOG(INFO) << "Successfully deleted the powerwash marker file : "
- << kPowerwashMarkerFile;
+ << powerwash_marker_full_path;
} else {
PLOG(ERROR) << "Could not delete the powerwash marker file : "
- << kPowerwashMarkerFile;
+ << powerwash_marker_full_path;
}
// Delete the rollback save marker file if it existed.
@@ -661,4 +689,8 @@
return true;
}
+base::FilePath HardwareChromeOS::GetPowerwashMarkerFullPath() const {
+ return root_.Append(kPowerwashMarkerPath);
+}
+
} // namespace chromeos_update_engine
diff --git a/cros/hardware_chromeos.h b/cros/hardware_chromeos.h
index f68431e..bce8bc2 100644
--- a/cros/hardware_chromeos.h
+++ b/cros/hardware_chromeos.h
@@ -18,6 +18,7 @@
#define UPDATE_ENGINE_CROS_HARDWARE_CHROMEOS_H_
#include <memory>
+#include <optional>
#include <string>
#include <vector>
@@ -62,6 +63,7 @@
bool SetMaxFirmwareKeyRollforward(int firmware_max_rollforward) override;
bool SetMaxKernelKeyRollforward(int kernel_max_rollforward) override;
int GetPowerwashCount() const override;
+ std::optional<bool> IsPowerwashScheduledByUpdateEngine() const override;
// Must not be called prior to boot control initialization.
bool SchedulePowerwash(bool save_rollback_data) override;
bool CancelPowerwash() override;
@@ -91,6 +93,8 @@
bool ResetFWTryNextSlot() override;
+ base::FilePath GetPowerwashMarkerFullPath() const override;
+
private:
friend class HardwareChromeOSTest;
FRIEND_TEST(HardwareChromeOSTest, GeneratePowerwashCommandCheck);
diff --git a/cros/hardware_chromeos_unittest.cc b/cros/hardware_chromeos_unittest.cc
index f60337c..1cd58ee 100644
--- a/cros/hardware_chromeos_unittest.cc
+++ b/cros/hardware_chromeos_unittest.cc
@@ -17,6 +17,7 @@
#include "update_engine/cros/hardware_chromeos.h"
#include <memory>
+#include <optional>
#include <utility>
#include <base/files/file_util.h>
@@ -467,4 +468,57 @@
ASSERT_FALSE(result);
}
+TEST_F(HardwareChromeOSTest, IsPowerwashScheduledByUpdateEngineValidReason) {
+ base::FilePath root_path = root_dir_.GetPath();
+ hardware_.SetRootForTest(root_path);
+ base::FilePath marker_path = hardware_.GetPowerwashMarkerFullPath();
+ std::string marker_contents = "safe reason=update_engine\n test_key";
+ ASSERT_TRUE(brillo::TouchFile(marker_path));
+ ASSERT_TRUE(base::WriteFile(marker_path, marker_contents));
+
+ std::optional<bool> result = hardware_.IsPowerwashScheduledByUpdateEngine();
+
+ EXPECT_TRUE(result);
+ EXPECT_TRUE(result.value());
+}
+
+TEST_F(HardwareChromeOSTest, IsPowerwashScheduledByUpdateEngineNoMarker) {
+ base::FilePath root_path = root_dir_.GetPath();
+ hardware_.SetRootForTest(root_path);
+ base::FilePath marker_path = hardware_.GetPowerwashMarkerFullPath();
+ ASSERT_FALSE(base::PathExists(marker_path));
+
+ std::optional<bool> result = hardware_.IsPowerwashScheduledByUpdateEngine();
+
+ EXPECT_FALSE(result);
+}
+
+TEST_F(HardwareChromeOSTest, IsPowerwashScheduledByUpdateEngineNoReasonKey) {
+ base::FilePath root_path = root_dir_.GetPath();
+ hardware_.SetRootForTest(root_path);
+ base::FilePath marker_path = hardware_.GetPowerwashMarkerFullPath();
+ std::string marker_contents = "safe reason test_key";
+ ASSERT_TRUE(brillo::TouchFile(marker_path));
+ ASSERT_TRUE(base::WriteFile(marker_path, marker_contents));
+
+ std::optional<bool> result = hardware_.IsPowerwashScheduledByUpdateEngine();
+
+ EXPECT_TRUE(result);
+ EXPECT_FALSE(result.value());
+}
+
+TEST_F(HardwareChromeOSTest, IsPowerwashScheduledByUpdateEngineEmptyReason) {
+ base::FilePath root_path = root_dir_.GetPath();
+ hardware_.SetRootForTest(root_path);
+ base::FilePath marker_path = hardware_.GetPowerwashMarkerFullPath();
+ std::string marker_contents = "safe reason=\n test_key";
+ ASSERT_TRUE(brillo::TouchFile(marker_path));
+ ASSERT_TRUE(base::WriteFile(marker_path, marker_contents));
+
+ std::optional<bool> result = hardware_.IsPowerwashScheduledByUpdateEngine();
+
+ EXPECT_TRUE(result);
+ EXPECT_FALSE(result.value());
+}
+
} // namespace chromeos_update_engine
diff --git a/cros/update_attempter.cc b/cros/update_attempter.cc
index d78eef9..d0eb4a0 100644
--- a/cros/update_attempter.cc
+++ b/cros/update_attempter.cc
@@ -1779,9 +1779,19 @@
}
LOG(INFO) << "Clearing powerwash and rollback flags, if any.";
- if (!SystemState::Get()->hardware()->CancelPowerwash()) {
- LOG(WARNING) << "Failed to cancel powerwash. Continuing anyway.";
- success = false;
+ const std::optional<bool> is_powerwash_scheduled_by_update_engine =
+ SystemState::Get()->hardware()->IsPowerwashScheduledByUpdateEngine();
+ if (!is_powerwash_scheduled_by_update_engine) {
+ LOG(INFO) << "Powerwash is not scheduled, continuing.";
+ } else if (!is_powerwash_scheduled_by_update_engine.value()) {
+ LOG(INFO) << "Not cancelling powerwash. Either not initiated by update "
+ "engine or there was a parsing error.";
+ } else {
+ LOG(INFO) << "Cancelling powerwash that was initiated by update engine.";
+ if (!SystemState::Get()->hardware()->CancelPowerwash()) {
+ LOG(WARNING) << "Failed to cancel powerwash. Continuing anyway.";
+ success = false;
+ }
}
SystemState::Get()->payload_state()->SetRollbackHappened(false);
diff --git a/cros/update_attempter.h b/cros/update_attempter.h
index 5da200e..7df9d81 100644
--- a/cros/update_attempter.h
+++ b/cros/update_attempter.h
@@ -374,6 +374,8 @@
FRIEND_TEST(UpdateAttempterTest, FirstUpdateBeforeReboot);
FRIEND_TEST(UpdateAttempterTest, InvalidateLastUpdate);
FRIEND_TEST(UpdateAttempterTest, InvalidateLastPowerwashUpdate);
+ FRIEND_TEST(UpdateAttempterTest, InvalidateLastUpdateNoPowerwashFile);
+ FRIEND_TEST(UpdateAttempterTest, InvalidateLastUpdateExternalPowerwash);
FRIEND_TEST(UpdateAttempterTest, ConsecutiveUpdateBeforeRebootSuccess);
FRIEND_TEST(UpdateAttempterTest, ConsecutiveUpdateBeforeRebootLimited);
FRIEND_TEST(UpdateAttempterTest, ConsecutiveUpdateFailureMetric);
diff --git a/cros/update_attempter_unittest.cc b/cros/update_attempter_unittest.cc
index 41da85d..6d0a186 100644
--- a/cros/update_attempter_unittest.cc
+++ b/cros/update_attempter_unittest.cc
@@ -22,6 +22,7 @@
#include <limits>
#include <map>
#include <memory>
+#include <optional>
#include <string>
#include <unordered_set>
@@ -2597,6 +2598,9 @@
bool save_rollback_data = true;
FakeSystemState::Get()->fake_hardware()->SchedulePowerwash(
save_rollback_data);
+ FakeSystemState::Get()
+ ->fake_hardware()
+ ->SetIsPowerwashScheduledByUpdateEngine(true);
EXPECT_TRUE(FakeSystemState::Get()->fake_hardware()->IsPowerwashScheduled());
FakeSystemState::Get()->fake_clock()->SetBootTime(Time::FromTimeT(42));
attempter_.WriteUpdateCompletedMarker();
@@ -2616,6 +2620,60 @@
EXPECT_TRUE(FakeSystemState::Get()->fake_hardware()->IsFWTryNextSlotReset());
}
+TEST_F(UpdateAttempterTest, InvalidateLastUpdateNoPowerwashFile) {
+ // Mock a previous update.
+ bool save_rollback_data = true;
+ FakeSystemState::Get()->fake_hardware()->SchedulePowerwash(
+ save_rollback_data);
+ EXPECT_TRUE(FakeSystemState::Get()->fake_hardware()->IsPowerwashScheduled());
+ FakeSystemState::Get()
+ ->fake_hardware()
+ ->SetIsPowerwashScheduledByUpdateEngine(std::nullopt);
+ FakeSystemState::Get()->fake_clock()->SetBootTime(Time::FromTimeT(42));
+ attempter_.WriteUpdateCompletedMarker();
+ EXPECT_TRUE(attempter_.GetBootTimeAtUpdate(nullptr));
+ attempter_.status_ = UpdateStatus::UPDATED_NEED_REBOOT;
+ EXPECT_CALL(*FakeSystemState::Get()->mock_payload_state(),
+ SetRollbackHappened(false))
+ .Times(1);
+
+ // Invalidate an update.
+ MockAction action;
+ attempter_.ActionCompleted(
+ nullptr, &action, ErrorCode::kInvalidateLastUpdate);
+
+ EXPECT_FALSE(attempter_.GetBootTimeAtUpdate(nullptr));
+ EXPECT_TRUE(FakeSystemState::Get()->fake_hardware()->IsPowerwashScheduled());
+ EXPECT_TRUE(FakeSystemState::Get()->fake_hardware()->IsFWTryNextSlotReset());
+}
+
+TEST_F(UpdateAttempterTest, InvalidateLastUpdateExternalPowerwash) {
+ // Mock a previous update.
+ bool save_rollback_data = true;
+ FakeSystemState::Get()->fake_hardware()->SchedulePowerwash(
+ save_rollback_data);
+ EXPECT_TRUE(FakeSystemState::Get()->fake_hardware()->IsPowerwashScheduled());
+ FakeSystemState::Get()
+ ->fake_hardware()
+ ->SetIsPowerwashScheduledByUpdateEngine(false);
+ FakeSystemState::Get()->fake_clock()->SetBootTime(Time::FromTimeT(42));
+ attempter_.WriteUpdateCompletedMarker();
+ EXPECT_TRUE(attempter_.GetBootTimeAtUpdate(nullptr));
+ attempter_.status_ = UpdateStatus::UPDATED_NEED_REBOOT;
+ EXPECT_CALL(*FakeSystemState::Get()->mock_payload_state(),
+ SetRollbackHappened(false))
+ .Times(1);
+
+ // Invalidate an update.
+ MockAction action;
+ attempter_.ActionCompleted(
+ nullptr, &action, ErrorCode::kInvalidateLastUpdate);
+
+ EXPECT_FALSE(attempter_.GetBootTimeAtUpdate(nullptr));
+ EXPECT_TRUE(FakeSystemState::Get()->fake_hardware()->IsPowerwashScheduled());
+ EXPECT_TRUE(FakeSystemState::Get()->fake_hardware()->IsFWTryNextSlotReset());
+}
+
TEST_F(UpdateAttempterTest, CalculateDlcParamsRemoveStaleMetadata) {
string dlc_id = "dlc0";
auto active_key =