update_engine: added CanRollback dbus method
Added a method to the update engine to check if a rollback
partition is available and can be booted from.
update_engine_client is also updated to call the function
when --can_rollback paramater is specified
BUG=chromium:343301
TEST=Ran "update_engine_client --can_rollback" and unit tests
Change-Id: If3fcb29a0067069a22812f60e9b67c6fdbbd18bd
Reviewed-on: https://chromium-review.googlesource.com/187157
Tested-by: Alex Vakulenko <avakulenko@chromium.org>
Reviewed-by: Don Garrett <dgarrett@chromium.org>
Commit-Queue: Alex Vakulenko <avakulenko@chromium.org>
diff --git a/UpdateEngine.conf b/UpdateEngine.conf
index 8c04eae..ac40e9d 100644
--- a/UpdateEngine.conf
+++ b/UpdateEngine.conf
@@ -17,6 +17,9 @@
send_member="AttemptRollback"/>
<allow send_destination="org.chromium.UpdateEngine"
send_interface="org.chromium.UpdateEngineInterface"
+ send_member="CanRollback"/>
+ <allow send_destination="org.chromium.UpdateEngine"
+ send_interface="org.chromium.UpdateEngineInterface"
send_member="ResetStatus"/>
<allow send_destination="org.chromium.UpdateEngine"
send_interface="org.chromium.UpdateEngineInterface"
diff --git a/dbus_service.cc b/dbus_service.cc
index f75823c..5562f4e 100644
--- a/dbus_service.cc
+++ b/dbus_service.cc
@@ -192,6 +192,16 @@
return TRUE;
}
+gboolean update_engine_service_can_rollback(UpdateEngineService* self,
+ gboolean* out_can_rollback,
+ GError **error)
+{
+ LOG(INFO) << "Checking for a rollback partition.";
+ *out_can_rollback = self->system_state_->update_attempter()->CanRollback();
+ return TRUE;
+}
+
+
gboolean update_engine_service_reset_status(UpdateEngineService* self,
GError **error) {
if (!self->system_state_->update_attempter()->ResetStatus()) {
diff --git a/dbus_service.h b/dbus_service.h
index 5d0f500..32bb599 100644
--- a/dbus_service.h
+++ b/dbus_service.h
@@ -62,6 +62,13 @@
gboolean powerwash,
GError **error);
+// Checks if the system rollback is available by verifying if the secondary
+// system partition is valid and bootable.
+gboolean update_engine_service_can_rollback(
+ UpdateEngineService* self,
+ gboolean* out_can_rollback,
+ GError **error);
+
gboolean update_engine_service_reset_status(UpdateEngineService* self,
GError **error);
diff --git a/fake_hardware.h b/fake_hardware.h
index 97ab8dd..6f4be77 100644
--- a/fake_hardware.h
+++ b/fake_hardware.h
@@ -17,6 +17,7 @@
FakeHardware()
: kernel_device_("/dev/sdz4"),
boot_device_("/dev/sdz5"),
+ bootable_devices_{"/dev/sdz4", "/dev/sdz5"},
is_official_build_(true),
is_normal_boot_mode_(true),
hardware_class_("Fake HWID BLAH-1234"),
@@ -26,6 +27,9 @@
// HardwareInterface methods.
virtual const std::string BootKernelDevice() { return kernel_device_; }
virtual const std::string BootDevice() { return boot_device_; }
+ virtual std::vector<std::string> GetKernelDevices() override
+ { return bootable_devices_; }
+
virtual bool IsKernelBootable(const std::string& kernel_device,
bool* bootable)
{ std::map<std::string, bool>::const_iterator i =
@@ -70,6 +74,7 @@
private:
std::string kernel_device_;
std::string boot_device_;
+ std::vector<std::string> bootable_devices_;
std::map<std::string, bool> is_bootable_;
bool is_official_build_;
bool is_normal_boot_mode_;
diff --git a/hardware.cc b/hardware.cc
index 9f3fe00..d0e0fc0 100644
--- a/hardware.cc
+++ b/hardware.cc
@@ -76,6 +76,33 @@
return true;
}
+std::vector<std::string> Hardware::GetKernelDevices() {
+ LOG(INFO) << "GetAllKernelDevices";
+
+ std::string disk_name = utils::GetDiskName(Hardware::BootKernelDevice());
+ if(disk_name.empty()) {
+ LOG(ERROR) << "Failed to get the cuurent kernel boot disk name";
+ return std::vector<std::string>();
+ }
+
+ std::vector<std::string> devices;
+ const int slot_count = 2; // Use only partition slots A and B
+ devices.reserve(slot_count);
+ for(int slot = 0; slot < slot_count; slot++) {
+ int partition_num = (slot + 1) * 2; // for now, only #2, #4
+ std::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 std::string& kernel_device) {
LOG(INFO) << "MarkPartitionUnbootable: " << kernel_device;
diff --git a/hardware.h b/hardware.h
index 8168d9c..cd7c8d5 100644
--- a/hardware.h
+++ b/hardware.h
@@ -20,6 +20,7 @@
// HardwareInterface methods.
virtual const std::string BootKernelDevice();
virtual const std::string BootDevice();
+ virtual std::vector<std::string> GetKernelDevices() override;
virtual bool IsKernelBootable(const std::string& kernel_device,
bool* bootable);
virtual bool MarkKernelUnbootable(const std::string& kernel_device);
diff --git a/hardware_interface.h b/hardware_interface.h
index 29baec4..58157d0 100644
--- a/hardware_interface.h
+++ b/hardware_interface.h
@@ -6,6 +6,7 @@
#define CHROMEOS_PLATFORM_UPDATE_ENGINE_HARDWARE_INTERFACE_H__
#include <string>
+#include <vector>
namespace chromeos_update_engine {
@@ -23,6 +24,9 @@
// Returns the currently booted rootfs partition. "/dev/sda3", for example.
virtual const std::string BootDevice() = 0;
+ // Returns a list of all kernel partitions available (whether bootable or not)
+ virtual std::vector<std::string> GetKernelDevices() = 0;
+
// Is the specified kernel partition currently bootable, based on GPT flags?
// Returns success.
virtual bool IsKernelBootable(const std::string& kernel_device,
diff --git a/mock_hardware.h b/mock_hardware.h
index 6a27488..8257b91 100644
--- a/mock_hardware.h
+++ b/mock_hardware.h
@@ -22,6 +22,9 @@
ON_CALL(*this, BootDevice())
.WillByDefault(testing::Invoke(&fake_,
&FakeHardware::BootDevice));
+ ON_CALL(*this, GetKernelDevices())
+ .WillByDefault(testing::Invoke(&fake_,
+ &FakeHardware::GetKernelDevices));
ON_CALL(*this, IsKernelBootable(testing::_, testing::_))
.WillByDefault(testing::Invoke(&fake_,
&FakeHardware::IsKernelBootable));
@@ -50,6 +53,7 @@
// Hardware overrides.
MOCK_METHOD0(BootKernelDevice, const std::string());
MOCK_METHOD0(BootDevice, const std::string());
+ MOCK_METHOD0(GetKernelDevices, std::vector<std::string>());
MOCK_METHOD2(IsKernelBootable,
bool(const std::string& kernel_device, bool* bootable));
MOCK_METHOD1(MarkKernelUnbootable,
diff --git a/update_attempter.cc b/update_attempter.cc
index 7b34cee..dcde2cf 100644
--- a/update_attempter.cc
+++ b/update_attempter.cc
@@ -7,6 +7,7 @@
#include <string>
#include <tr1/memory>
#include <vector>
+#include <algorithm>
#include <base/file_util.h>
#include <base/logging.h>
@@ -788,6 +789,35 @@
return true;
}
+bool UpdateAttempter::CanRollback() const {
+ std::vector<std::string> kernel_devices =
+ system_state_->hardware()->GetKernelDevices();
+
+ std::string boot_kernel_device =
+ system_state_->hardware()->BootKernelDevice();
+
+ 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 false;
+ }
+
+ for (std::string const& device_name : kernel_devices) {
+ if (device_name != *current) {
+ bool bootable = false;
+ if (system_state_->hardware()->IsKernelBootable(device_name, &bootable) &&
+ bootable) {
+ return true;
+ }
+ }
+ }
+
+ return false;
+}
+
void UpdateAttempter::CheckForUpdate(const string& app_version,
const string& omaha_url,
bool interactive) {
diff --git a/update_attempter.h b/update_attempter.h
index 70381d9..d5c6adb 100644
--- a/update_attempter.h
+++ b/update_attempter.h
@@ -149,6 +149,10 @@
// the partitions to roll back to (used in testing). Returns True on success.
bool Rollback(bool powerwash, std::string* install_path);
+ // This is the internal entry point for checking if a valid rollback
+ // partition exists.
+ bool CanRollback() const;
+
// Initiates a reboot if the current state is
// UPDATED_NEED_REBOOT. Returns true on sucess, false otherwise.
bool RebootIfNeeded();
diff --git a/update_engine.xml b/update_engine.xml
index 5a2b722..964ea62 100644
--- a/update_engine.xml
+++ b/update_engine.xml
@@ -26,6 +26,9 @@
<method name="AttemptRollback">
<arg type="b" name="powerwash" />
</method>
+ <method name="CanRollback">
+ <arg type="b" name="can_rollback" direction="out" />
+ </method>
<method name="ResetStatus">
</method>
<method name="GetStatus">
diff --git a/update_engine_client.cc b/update_engine_client.cc
index fc06da2..6bd3d9f 100644
--- a/update_engine_client.cc
+++ b/update_engine_client.cc
@@ -42,6 +42,8 @@
DEFINE_bool(reboot, false, "Initiate a reboot if needed.");
DEFINE_bool(reset_status, false, "Sets the status in update_engine to idle.");
DEFINE_bool(rollback, false, "Perform a rollback to the previous partition.");
+DEFINE_bool(can_rollback, false, "Shows whether rollback partition "
+ "is available.");
DEFINE_bool(show_channel, false, "Show the current and target channels.");
DEFINE_bool(show_p2p_update, false,
"Show the current setting for peer-to-peer update sharing.");
@@ -207,6 +209,20 @@
return true;
}
+bool CanRollback() {
+ DBusGProxy* proxy;
+ GError* error = NULL;
+
+ CHECK(GetProxy(&proxy));
+
+ gboolean can_rollback = FALSE;
+ gboolean rc = update_engine_client_can_rollback(proxy,
+ &can_rollback,
+ &error);
+ CHECK_EQ(rc, TRUE) << "Error while querying rollback partition availabilty: "
+ << GetAndFreeGError(&error);
+ return can_rollback;
+}
bool CheckForUpdates(const string& app_version, const string& omaha_url) {
DBusGProxy* proxy;
GError* error = NULL;
@@ -412,6 +428,13 @@
}
}
+ // Show the rollback availability.
+ if (FLAGS_can_rollback) {
+ bool can_rollback = CanRollback();
+ LOG(INFO) << "Rollback partition: "
+ << (can_rollback ? "AVAILABLE" : "UNAVAILABLE");
+ }
+
// Show the current P2P enabled setting.
if (FLAGS_show_p2p_update) {
bool enabled = GetP2PUpdatePermission();