diff options
Diffstat (limited to 'cros/update_engine_client.cc')
-rw-r--r-- | cros/update_engine_client.cc | 601 |
1 files changed, 601 insertions, 0 deletions
diff --git a/cros/update_engine_client.cc b/cros/update_engine_client.cc new file mode 100644 index 00000000..6f20f11a --- /dev/null +++ b/cros/update_engine_client.cc @@ -0,0 +1,601 @@ +// +// Copyright (C) 2012 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 <inttypes.h> +#include <sysexits.h> +#include <unistd.h> + +#include <memory> +#include <string> +#include <vector> + +#include <base/bind.h> +#include <base/command_line.h> +#include <base/logging.h> +#include <base/macros.h> +#include <base/strings/string_number_conversions.h> +#include <base/strings/string_split.h> +#include <base/threading/platform_thread.h> +#include <base/threading/thread_task_runner_handle.h> +#include <brillo/daemons/daemon.h> +#include <brillo/flag_helper.h> +#include <brillo/key_value_store.h> + +#include "update_engine/client.h" +#include "update_engine/common/error_code.h" +#include "update_engine/common/error_code_utils.h" +#include "update_engine/cros/omaha_utils.h" +#include "update_engine/status_update_handler.h" +#include "update_engine/update_status.h" +#include "update_engine/update_status_utils.h" + +using brillo::KeyValueStore; +using chromeos_update_engine::EolDate; +using chromeos_update_engine::EolDateToString; +using chromeos_update_engine::ErrorCode; +using chromeos_update_engine::UpdateEngineStatusToString; +using chromeos_update_engine::UpdateStatusToString; +using chromeos_update_engine::utils::ErrorCodeToString; +using std::string; +using std::unique_ptr; +using std::vector; +using update_engine::UpdateEngineStatus; +using update_engine::UpdateStatus; + +namespace { + +// Constant to signal that we need to continue running the daemon after +// initialization. +const int kContinueRunning = -1; + +// The ShowStatus request will be retried `kShowStatusRetryCount` times at +// `kShowStatusRetryInterval` second intervals on failure. +const int kShowStatusRetryCount = 30; +const int kShowStatusRetryIntervalInSeconds = 2; + +class UpdateEngineClient : public brillo::Daemon { + public: + UpdateEngineClient(int argc, char** argv) : argc_(argc), argv_(argv) {} + + ~UpdateEngineClient() override = default; + + protected: + int OnInit() override { + int ret = Daemon::OnInit(); + if (ret != EX_OK) + return ret; + + client_ = update_engine::UpdateEngineClient::CreateInstance(); + + if (!client_) { + LOG(ERROR) << "UpdateEngineService not available."; + return 1; + } + + // We can't call QuitWithExitCode from OnInit(), so we delay the execution + // of the ProcessFlags method after the Daemon initialization is done. + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, + base::Bind(&UpdateEngineClient::ProcessFlagsAndExit, + base::Unretained(this))); + return EX_OK; + } + + private: + // Show the status of the update engine in stdout. + bool ShowStatus(); + + // Return whether we need to reboot. 0 if reboot is needed, 1 if an error + // occurred, 2 if no reboot is needed. + int GetNeedReboot(); + + // Main method that parses and triggers all the actions based on the passed + // flags. Returns the exit code of the program of kContinueRunning if it + // should not exit. + int ProcessFlags(); + + // Processes the flags and exits the program accordingly. + void ProcessFlagsAndExit(); + + // Copy of argc and argv passed to main(). + int argc_; + char** argv_; + + // Library-based client + unique_ptr<update_engine::UpdateEngineClient> client_; + + // Pointers to handlers for cleanup + vector<unique_ptr<update_engine::StatusUpdateHandler>> handlers_; + + DISALLOW_COPY_AND_ASSIGN(UpdateEngineClient); +}; + +class ExitingStatusUpdateHandler : public update_engine::StatusUpdateHandler { + public: + ~ExitingStatusUpdateHandler() override = default; + + void IPCError(const string& error) override; +}; + +void ExitingStatusUpdateHandler::IPCError(const string& error) { + LOG(ERROR) << error; + exit(1); +} + +class WatchingStatusUpdateHandler : public ExitingStatusUpdateHandler { + public: + ~WatchingStatusUpdateHandler() override = default; + + void HandleStatusUpdate(const UpdateEngineStatus& status) override; +}; + +void WatchingStatusUpdateHandler::HandleStatusUpdate( + const UpdateEngineStatus& status) { + LOG(INFO) << "Got status update: " << UpdateEngineStatusToString(status); +} + +bool UpdateEngineClient::ShowStatus() { + UpdateEngineStatus status; + int retry_count = kShowStatusRetryCount; + while (retry_count > 0) { + if (client_->GetStatus(&status)) { + break; + } + if (--retry_count == 0) { + return false; + } + LOG(WARNING) + << "Failed to get the update_engine status. This can happen when the" + " update_engine is busy doing a heavy operation or if the" + " update-engine service is down. If it doesn't resolve, a restart of" + " the update-engine service is needed." + " Will try " + << retry_count << " more times!"; + base::PlatformThread::Sleep( + base::TimeDelta::FromSeconds(kShowStatusRetryIntervalInSeconds)); + } + + printf("%s", UpdateEngineStatusToString(status).c_str()); + + return true; +} + +int UpdateEngineClient::GetNeedReboot() { + UpdateEngineStatus status; + if (!client_->GetStatus(&status)) { + return 1; + } + + if (status.status == UpdateStatus::UPDATED_NEED_REBOOT) { + return 0; + } + + return 2; +} + +class UpdateWaitHandler : public ExitingStatusUpdateHandler { + public: + explicit UpdateWaitHandler(bool exit_on_error, + update_engine::UpdateEngineClient* client) + : exit_on_error_(exit_on_error), client_(client) {} + + ~UpdateWaitHandler() override = default; + + void HandleStatusUpdate(const UpdateEngineStatus& status) override; + + private: + bool exit_on_error_; + update_engine::UpdateEngineClient* client_; +}; + +void UpdateWaitHandler::HandleStatusUpdate(const UpdateEngineStatus& status) { + if (exit_on_error_ && status.status == UpdateStatus::IDLE) { + int last_attempt_error = static_cast<int>(ErrorCode::kSuccess); + ErrorCode code = ErrorCode::kSuccess; + if (client_ && client_->GetLastAttemptError(&last_attempt_error)) + code = static_cast<ErrorCode>(last_attempt_error); + + LOG(ERROR) << "Update failed, current operation is " + << UpdateStatusToString(status.status) << ", last error code is " + << ErrorCodeToString(code) << "(" << last_attempt_error << ")"; + exit(1); + } + if (status.status == UpdateStatus::UPDATED_NEED_REBOOT) { + LOG(INFO) << "Update succeeded -- reboot needed."; + exit(0); + } +} + +int UpdateEngineClient::ProcessFlags() { + DEFINE_string(app_version, "", "Force the current app version."); + DEFINE_string(channel, + "", + "Set the target channel. The device will be powerwashed if the " + "target channel is more stable than the current channel unless " + "--nopowerwash is specified."); + DEFINE_bool(check_for_update, false, "Initiate check for updates."); + DEFINE_string( + cohort_hint, "", "Set the current cohort hint to the passed value."); + DEFINE_bool(follow, + false, + "Wait for any update operations to complete." + "Exit status is 0 if the update succeeded, and 1 otherwise."); + DEFINE_bool(interactive, true, "Mark the update request as interactive."); + DEFINE_string(omaha_url, "", "The URL of the Omaha update server."); + DEFINE_string(p2p_update, + "", + "Enables (\"yes\") or disables (\"no\") the peer-to-peer update" + " sharing."); + 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. The device will " + "be powerwashed unless --nopowerwash is specified."); + 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_cohort_hint, false, "Show the current cohort hint."); + DEFINE_bool(show_p2p_update, + false, + "Show the current setting for peer-to-peer update sharing."); + DEFINE_bool(show_update_over_cellular, + false, + "Show the current setting for updates over cellular networks."); + DEFINE_bool(status, false, "Print the status to stdout."); + DEFINE_bool(update, + false, + "Forces an update and waits for it to complete. " + "Implies --follow."); + DEFINE_string(update_over_cellular, + "", + "Enables (\"yes\") or disables (\"no\") the updates over " + "cellular networks."); + DEFINE_bool(watch_for_updates, + false, + "Listen for status updates and print them to the screen."); + DEFINE_bool(prev_version, + false, + "Show the previous OS version used before the update reboot."); + DEFINE_bool(last_attempt_error, false, "Show the last attempt error."); + DEFINE_bool(eol_status, false, "Show the current end-of-life status."); + + // Boilerplate init commands. + base::CommandLine::Init(argc_, argv_); + brillo::FlagHelper::Init(argc_, argv_, "A/B Update Engine Client"); + + // Ensure there are no positional arguments. + const vector<string> positional_args = + base::CommandLine::ForCurrentProcess()->GetArgs(); + if (!positional_args.empty()) { + LOG(ERROR) << "Found a positional argument '" << positional_args.front() + << "'. If you want to pass a value to a flag, pass it as " + "--flag=value."; + return 1; + } + + // Update the status if requested. + if (FLAGS_reset_status) { + LOG(INFO) << "Setting Update Engine status to idle ..."; + + if (client_->ResetStatus()) { + LOG(INFO) << "ResetStatus succeeded; to undo partition table changes " + "run:\n" + "(D=$(rootdev -d) P=$(rootdev -s); cgpt p -i$(($(echo " + "${P#$D} | sed 's/^[^0-9]*//')-1)) $D;)"; + } else { + LOG(ERROR) << "ResetStatus failed"; + return 1; + } + } + + // Changes the current update over cellular network setting. + if (!FLAGS_update_over_cellular.empty()) { + bool allowed = FLAGS_update_over_cellular == "yes"; + if (!allowed && FLAGS_update_over_cellular != "no") { + LOG(ERROR) << "Unknown option: \"" << FLAGS_update_over_cellular + << "\". Please specify \"yes\" or \"no\"."; + } else { + if (!client_->SetUpdateOverCellularPermission(allowed)) { + LOG(ERROR) << "Error setting the update over cellular setting."; + return 1; + } + } + } + + // Show the current update over cellular network setting. + if (FLAGS_show_update_over_cellular) { + bool allowed; + + if (!client_->GetUpdateOverCellularPermission(&allowed)) { + LOG(ERROR) << "Error getting the update over cellular setting."; + return 1; + } + + LOG(INFO) << "Current update over cellular network setting: " + << (allowed ? "ENABLED" : "DISABLED"); + } + + // Change/show the cohort hint. + bool set_cohort_hint = + base::CommandLine::ForCurrentProcess()->HasSwitch("cohort_hint"); + if (set_cohort_hint) { + LOG(INFO) << "Setting cohort hint to: \"" << FLAGS_cohort_hint << "\""; + if (!client_->SetCohortHint(FLAGS_cohort_hint)) { + LOG(ERROR) << "Error setting the cohort hint."; + return 1; + } + } + + if (FLAGS_show_cohort_hint || set_cohort_hint) { + string cohort_hint; + if (!client_->GetCohortHint(&cohort_hint)) { + LOG(ERROR) << "Error getting the cohort hint."; + return 1; + } + + LOG(INFO) << "Current cohort hint: \"" << cohort_hint << "\""; + } + + if (!FLAGS_powerwash && !FLAGS_rollback && FLAGS_channel.empty()) { + LOG(ERROR) << "powerwash flag only makes sense rollback or channel change"; + return 1; + } + + // Change the P2P enabled setting. + if (!FLAGS_p2p_update.empty()) { + bool enabled = FLAGS_p2p_update == "yes"; + if (!enabled && FLAGS_p2p_update != "no") { + LOG(ERROR) << "Unknown option: \"" << FLAGS_p2p_update + << "\". Please specify \"yes\" or \"no\"."; + } else { + if (!client_->SetP2PUpdatePermission(enabled)) { + LOG(ERROR) << "Error setting the peer-to-peer update setting."; + return 1; + } + } + } + + // Show the rollback availability. + if (FLAGS_can_rollback) { + string rollback_partition; + + if (!client_->GetRollbackPartition(&rollback_partition)) { + LOG(ERROR) << "Error while querying rollback partition availability."; + return 1; + } + + bool can_rollback = true; + if (rollback_partition.empty()) { + rollback_partition = "UNAVAILABLE"; + can_rollback = false; + } else { + rollback_partition = "AVAILABLE: " + rollback_partition; + } + + LOG(INFO) << "Rollback partition: " << rollback_partition; + if (!can_rollback) { + return 1; + } + } + + // Show the current P2P enabled setting. + if (FLAGS_show_p2p_update) { + bool enabled; + + if (!client_->GetP2PUpdatePermission(&enabled)) { + LOG(ERROR) << "Error getting the peer-to-peer update setting."; + return 1; + } + + LOG(INFO) << "Current update using P2P setting: " + << (enabled ? "ENABLED" : "DISABLED"); + } + + // First, update the target channel if requested. + if (!FLAGS_channel.empty()) { + if (!client_->SetTargetChannel(FLAGS_channel, FLAGS_powerwash)) { + LOG(ERROR) << "Error setting the channel."; + return 1; + } + + LOG(INFO) << "Channel permanently set to: " << FLAGS_channel; + } + + // Show the current and target channels if requested. + if (FLAGS_show_channel) { + string current_channel; + string target_channel; + + if (!client_->GetChannel(¤t_channel)) { + LOG(ERROR) << "Error getting the current channel."; + return 1; + } + + if (!client_->GetTargetChannel(&target_channel)) { + LOG(ERROR) << "Error getting the target channel."; + return 1; + } + + LOG(INFO) << "Current Channel: " << current_channel; + + if (!target_channel.empty()) + LOG(INFO) << "Target Channel (pending update): " << target_channel; + } + + bool do_update_request = FLAGS_check_for_update || FLAGS_update || + !FLAGS_app_version.empty() || + !FLAGS_omaha_url.empty(); + if (FLAGS_update) + FLAGS_follow = true; + + if (do_update_request && FLAGS_rollback) { + LOG(ERROR) << "Incompatible flags specified with rollback." + << "Rollback should not include update-related flags."; + return 1; + } + + if (FLAGS_rollback) { + LOG(INFO) << "Requesting rollback."; + if (!client_->Rollback(FLAGS_powerwash)) { + LOG(ERROR) << "Rollback request failed."; + return 1; + } + } + + // Initiate an update check, if necessary. + if (do_update_request) { + LOG_IF(WARNING, FLAGS_reboot) << "-reboot flag ignored."; + string app_version = FLAGS_app_version; + if (FLAGS_update && app_version.empty()) { + app_version = "ForcedUpdate"; + LOG(INFO) << "Forcing an update by setting app_version to ForcedUpdate."; + } + LOG(INFO) << "Initiating update check."; + if (!client_->AttemptUpdate( + app_version, FLAGS_omaha_url, FLAGS_interactive)) { + LOG(ERROR) << "Error checking for update."; + return 1; + } + } + + // These final options are all mutually exclusive with one another. + 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; + } + + if (FLAGS_status) { + LOG(INFO) << "Querying Update Engine status..."; + if (!ShowStatus()) { + LOG(ERROR) << "Failed to query status"; + return 1; + } + return 0; + } + + if (FLAGS_follow) { + LOG(INFO) << "Waiting for update to complete."; + auto handler = new UpdateWaitHandler(true, client_.get()); + handlers_.emplace_back(handler); + client_->RegisterStatusUpdateHandler(handler); + return kContinueRunning; + } + + if (FLAGS_watch_for_updates) { + LOG(INFO) << "Watching for status updates."; + auto handler = new WatchingStatusUpdateHandler(); + handlers_.emplace_back(handler); + client_->RegisterStatusUpdateHandler(handler); + return kContinueRunning; + } + + if (FLAGS_reboot) { + LOG(INFO) << "Requesting a reboot..."; + client_->RebootIfNeeded(); + return 0; + } + + if (FLAGS_prev_version) { + string prev_version; + + if (!client_->GetPrevVersion(&prev_version)) { + LOG(ERROR) << "Error getting previous version."; + } else { + LOG(INFO) << "Previous version = " << prev_version; + } + } + + if (FLAGS_is_reboot_needed) { + int ret = GetNeedReboot(); + + if (ret == 1) { + LOG(ERROR) << "Could not query the current operation."; + } + + return ret; + } + + if (FLAGS_block_until_reboot_is_needed) { + auto handler = new UpdateWaitHandler(false, nullptr); + handlers_.emplace_back(handler); + client_->RegisterStatusUpdateHandler(handler); + return kContinueRunning; + } + + if (FLAGS_last_attempt_error) { + int last_attempt_error; + if (!client_->GetLastAttemptError(&last_attempt_error)) { + LOG(ERROR) << "Error getting last attempt error."; + } else { + ErrorCode code = static_cast<ErrorCode>(last_attempt_error); + + KeyValueStore last_attempt_error_store; + last_attempt_error_store.SetString( + "ERROR_CODE", base::NumberToString(last_attempt_error)); + last_attempt_error_store.SetString("ERROR_MESSAGE", + ErrorCodeToString(code)); + printf("%s", last_attempt_error_store.SaveToString().c_str()); + } + } + + if (FLAGS_eol_status) { + UpdateEngineStatus status; + if (!client_->GetStatus(&status)) { + LOG(ERROR) << "Error GetStatus() for getting EOL info."; + } else { + EolDate eol_date_code = status.eol_date; + + KeyValueStore eol_status_store; + eol_status_store.SetString("EOL_DATE", EolDateToString(eol_date_code)); + printf("%s", eol_status_store.SaveToString().c_str()); + } + } + + return 0; +} + +void UpdateEngineClient::ProcessFlagsAndExit() { + int ret = ProcessFlags(); + if (ret != kContinueRunning) + QuitWithExitCode(ret); +} + +} // namespace + +int main(int argc, char** argv) { + UpdateEngineClient client(argc, argv); + return client.Run(); +} |