update_engine_client: Make it easy to write a reboot manager.
This CL adds options -block_until_reboot_is_needed and -is_reboot_needed
flags to update_engine_client. This makes it easy to write a simple
script that can be used to reboot the device when an update has been
applied. For example, something like this:
while true;
update_engine_client -block_until_reboot_is_needed
if "$?" != "0"; then
log -p user.critical "update_engine_client failed!"
else
# TODO: app-specific logic to figure out if now is a good time to reboot
reboot
fi
sleep 1
done
BUG=chromium:353871
TEST=Manually tested.
Change-Id: I8399498911b5eb68ceb0c493926ef685a8b89e82
Reviewed-on: https://chromium-review.googlesource.com/193651
Reviewed-by: Alex Deymo <deymo@chromium.org>
Commit-Queue: David Zeuthen <zeuthen@chromium.org>
Tested-by: David Zeuthen <zeuthen@chromium.org>
diff --git a/update_engine_client.cc b/update_engine_client.cc
index 3ee2a2a..ffe16e4 100644
--- a/update_engine_client.cc
+++ b/update_engine_client.cc
@@ -4,6 +4,7 @@
#include <string>
+#include <chromeos/dbus/service_constants.h>
#include <dbus/dbus.h>
#include <gflags/gflags.h>
#include <glib.h>
@@ -40,6 +41,10 @@
DEFINE_bool(powerwash, true, "When performing rollback or channel change, "
"do a powerwash or allow it respectively.");
DEFINE_bool(reboot, false, "Initiate a reboot if needed.");
+DEFINE_bool(is_reboot_needed, false, "Exit status 0 if reboot is needed, "
+ "2 if reboot is not needed or 1 if an error occurred.");
+DEFINE_bool(block_until_reboot_is_needed, false, "Blocks until reboot is "
+ "needed. Returns non-zero exit status if an error occurred.");
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 "
@@ -417,6 +422,113 @@
}
}
+bool CheckIfRebootIsNeeded(DBusGProxy *proxy, bool *out_reboot_needed) {
+ gint64 last_checked_time = 0;
+ gdouble progress = 0.0;
+ char* current_op = nullptr;
+ char* new_version = nullptr;
+ gint64 new_size = 0;
+ GError* error = nullptr;
+
+ if (!update_engine_client_get_status(proxy,
+ &last_checked_time,
+ &progress,
+ ¤t_op,
+ &new_version,
+ &new_size,
+ &error)) {
+ LOG(INFO) << "Error getting status: " << GetAndFreeGError(&error);
+ return false;
+ }
+ *out_reboot_needed =
+ (g_strcmp0(current_op,
+ update_engine::kUpdateStatusUpdatedNeedReboot) == 0);
+ g_free(current_op);
+ g_free(new_version);
+ return true;
+}
+
+// Determines if reboot is needed. The result is returned in
+// |out_reboot_needed|. Returns true if the check succeeded, false
+// otherwise.
+bool IsRebootNeeded(bool *out_reboot_needed) {
+ DBusGProxy* proxy = nullptr;
+ CHECK(GetProxy(&proxy));
+ bool ret = CheckIfRebootIsNeeded(proxy, out_reboot_needed);
+ g_object_unref(proxy);
+ return ret;
+}
+
+static void OnBlockUntilRebootStatusCallback(
+ DBusGProxy* proxy,
+ int64_t last_checked_time,
+ double progress,
+ const gchar* current_operation,
+ const gchar* new_version,
+ int64_t new_size,
+ void* user_data) {
+ GMainLoop *loop = reinterpret_cast<GMainLoop*>(user_data);
+ if (g_strcmp0(current_operation,
+ update_engine::kUpdateStatusUpdatedNeedReboot) == 0) {
+ g_main_loop_quit(loop);
+ }
+}
+
+bool CheckRebootNeeded(DBusGProxy *proxy, GMainLoop *loop) {
+ bool reboot_needed;
+ if (!CheckIfRebootIsNeeded(proxy, &reboot_needed))
+ return false;
+ if (reboot_needed)
+ return true;
+ // This will block until OnBlockUntilRebootStatusCallback() calls
+ // g_main_loop_quit().
+ g_main_loop_run(loop);
+ return true;
+}
+
+// Blocks until a reboot is needed. Returns true if waiting succeeded,
+// false if an error occurred.
+bool BlockUntilRebootIsNeeded() {
+ // The basic idea is to get a proxy, listen to signals and only then
+ // check the status. If no reboot is needed, just sit and wait for
+ // the StatusUpdate signal to convey that a reboot is pending.
+ DBusGProxy* proxy = nullptr;
+ CHECK(GetProxy(&proxy));
+ dbus_g_object_register_marshaller(
+ g_cclosure_marshal_generic,
+ G_TYPE_NONE,
+ G_TYPE_INT64,
+ G_TYPE_DOUBLE,
+ G_TYPE_STRING,
+ G_TYPE_STRING,
+ G_TYPE_INT64,
+ G_TYPE_INVALID);
+ dbus_g_proxy_add_signal(proxy,
+ update_engine::kStatusUpdate, // Signal name.
+ G_TYPE_INT64,
+ G_TYPE_DOUBLE,
+ G_TYPE_STRING,
+ G_TYPE_STRING,
+ G_TYPE_INT64,
+ G_TYPE_INVALID);
+ GMainLoop* loop = g_main_loop_new(nullptr, TRUE);
+ dbus_g_proxy_connect_signal(proxy,
+ update_engine::kStatusUpdate,
+ G_CALLBACK(OnBlockUntilRebootStatusCallback),
+ loop,
+ nullptr); // free_data_func.
+
+ bool ret = CheckRebootNeeded(proxy, loop);
+
+ dbus_g_proxy_disconnect_signal(proxy,
+ update_engine::kStatusUpdate,
+ G_CALLBACK(OnBlockUntilRebootStatusCallback),
+ loop);
+ g_main_loop_unref(loop);
+ g_object_unref(proxy);
+ return ret;
+}
+
} // namespace {}
int main(int argc, char** argv) {
@@ -534,10 +646,13 @@
}
// These final options are all mutually exclusive with one another.
- if (FLAGS_follow + FLAGS_watch_for_updates + FLAGS_reboot + FLAGS_status > 1)
+ if (FLAGS_follow + FLAGS_watch_for_updates + FLAGS_reboot +
+ FLAGS_status + FLAGS_is_reboot_needed +
+ FLAGS_block_until_reboot_is_needed > 1)
{
LOG(ERROR) << "Multiple exclusive options selected. "
<< "Select only one of --follow, --watch_for_updates, --reboot, "
+ << "--is_reboot_needed, --block_until_reboot_is_needed, "
<< "or --status.";
return 1;
}
@@ -578,6 +693,19 @@
<< GetKernelDevices();
}
+ if (FLAGS_is_reboot_needed) {
+ bool reboot_needed = false;
+ if (!IsRebootNeeded(&reboot_needed))
+ return 1;
+ else if (!reboot_needed)
+ return 2;
+ }
+
+ if (FLAGS_block_until_reboot_is_needed) {
+ if (!BlockUntilRebootIsNeeded())
+ return 1;
+ }
+
LOG(INFO) << "Done.";
return 0;
}