update_engine_client.cc 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657
  1. //
  2. // Copyright (C) 2012 The Android Open Source Project
  3. //
  4. // Licensed under the Apache License, Version 2.0 (the "License");
  5. // you may not use this file except in compliance with the License.
  6. // You may obtain a copy of the License at
  7. //
  8. // http://www.apache.org/licenses/LICENSE-2.0
  9. //
  10. // Unless required by applicable law or agreed to in writing, software
  11. // distributed under the License is distributed on an "AS IS" BASIS,
  12. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. // See the License for the specific language governing permissions and
  14. // limitations under the License.
  15. //
  16. #include <inttypes.h>
  17. #include <sysexits.h>
  18. #include <unistd.h>
  19. #include <memory>
  20. #include <string>
  21. #include <vector>
  22. #include <base/bind.h>
  23. #include <base/command_line.h>
  24. #include <base/logging.h>
  25. #include <base/macros.h>
  26. #include <base/strings/string_split.h>
  27. #include <base/threading/platform_thread.h>
  28. #include <brillo/daemons/daemon.h>
  29. #include <brillo/flag_helper.h>
  30. #include "update_engine/client.h"
  31. #include "update_engine/common/error_code.h"
  32. #include "update_engine/common/error_code_utils.h"
  33. #include "update_engine/omaha_utils.h"
  34. #include "update_engine/status_update_handler.h"
  35. #include "update_engine/update_status.h"
  36. #include "update_engine/update_status_utils.h"
  37. using chromeos_update_engine::EolStatus;
  38. using chromeos_update_engine::ErrorCode;
  39. using chromeos_update_engine::UpdateStatusToString;
  40. using chromeos_update_engine::utils::ErrorCodeToString;
  41. using std::string;
  42. using std::unique_ptr;
  43. using std::vector;
  44. using update_engine::UpdateStatus;
  45. namespace {
  46. // Constant to signal that we need to continue running the daemon after
  47. // initialization.
  48. const int kContinueRunning = -1;
  49. // The ShowStatus request will be retried `kShowStatusRetryCount` times at
  50. // `kShowStatusRetryInterval` second intervals on failure.
  51. const int kShowStatusRetryCount = 30;
  52. const int kShowStatusRetryIntervalInSeconds = 2;
  53. class UpdateEngineClient : public brillo::Daemon {
  54. public:
  55. UpdateEngineClient(int argc, char** argv) : argc_(argc), argv_(argv) {}
  56. ~UpdateEngineClient() override = default;
  57. protected:
  58. int OnInit() override {
  59. int ret = Daemon::OnInit();
  60. if (ret != EX_OK)
  61. return ret;
  62. client_ = update_engine::UpdateEngineClient::CreateInstance();
  63. if (!client_) {
  64. LOG(ERROR) << "UpdateEngineService not available.";
  65. return 1;
  66. }
  67. // We can't call QuitWithExitCode from OnInit(), so we delay the execution
  68. // of the ProcessFlags method after the Daemon initialization is done.
  69. base::MessageLoop::current()->task_runner()->PostTask(
  70. FROM_HERE,
  71. base::Bind(&UpdateEngineClient::ProcessFlagsAndExit,
  72. base::Unretained(this)));
  73. return EX_OK;
  74. }
  75. private:
  76. // Show the status of the update engine in stdout.
  77. bool ShowStatus();
  78. // Return whether we need to reboot. 0 if reboot is needed, 1 if an error
  79. // occurred, 2 if no reboot is needed.
  80. int GetNeedReboot();
  81. // Main method that parses and triggers all the actions based on the passed
  82. // flags. Returns the exit code of the program of kContinueRunning if it
  83. // should not exit.
  84. int ProcessFlags();
  85. // Processes the flags and exits the program accordingly.
  86. void ProcessFlagsAndExit();
  87. // Copy of argc and argv passed to main().
  88. int argc_;
  89. char** argv_;
  90. // Library-based client
  91. unique_ptr<update_engine::UpdateEngineClient> client_;
  92. // Pointers to handlers for cleanup
  93. vector<unique_ptr<update_engine::StatusUpdateHandler>> handlers_;
  94. DISALLOW_COPY_AND_ASSIGN(UpdateEngineClient);
  95. };
  96. class ExitingStatusUpdateHandler : public update_engine::StatusUpdateHandler {
  97. public:
  98. ~ExitingStatusUpdateHandler() override = default;
  99. void IPCError(const string& error) override;
  100. };
  101. void ExitingStatusUpdateHandler::IPCError(const string& error) {
  102. LOG(ERROR) << error;
  103. exit(1);
  104. }
  105. class WatchingStatusUpdateHandler : public ExitingStatusUpdateHandler {
  106. public:
  107. ~WatchingStatusUpdateHandler() override = default;
  108. void HandleStatusUpdate(int64_t last_checked_time,
  109. double progress,
  110. UpdateStatus current_operation,
  111. const string& new_version,
  112. int64_t new_size) override;
  113. };
  114. void WatchingStatusUpdateHandler::HandleStatusUpdate(
  115. int64_t last_checked_time,
  116. double progress,
  117. UpdateStatus current_operation,
  118. const string& new_version,
  119. int64_t new_size) {
  120. LOG(INFO) << "Got status update:";
  121. LOG(INFO) << " last_checked_time: " << last_checked_time;
  122. LOG(INFO) << " progress: " << progress;
  123. LOG(INFO) << " current_operation: "
  124. << UpdateStatusToString(current_operation);
  125. LOG(INFO) << " new_version: " << new_version;
  126. LOG(INFO) << " new_size: " << new_size;
  127. }
  128. bool UpdateEngineClient::ShowStatus() {
  129. int64_t last_checked_time = 0;
  130. double progress = 0.0;
  131. UpdateStatus current_op;
  132. string new_version;
  133. int64_t new_size = 0;
  134. int retry_count = kShowStatusRetryCount;
  135. while (retry_count > 0) {
  136. if (client_->GetStatus(&last_checked_time,
  137. &progress,
  138. &current_op,
  139. &new_version,
  140. &new_size)) {
  141. break;
  142. }
  143. if (--retry_count == 0) {
  144. return false;
  145. }
  146. LOG(WARNING) << "Will try " << retry_count << " more times!";
  147. base::PlatformThread::Sleep(
  148. base::TimeDelta::FromSeconds(kShowStatusRetryIntervalInSeconds));
  149. }
  150. printf("LAST_CHECKED_TIME=%" PRIi64
  151. "\nPROGRESS=%f\nCURRENT_OP=%s\n"
  152. "NEW_VERSION=%s\nNEW_SIZE=%" PRIi64 "\n",
  153. last_checked_time,
  154. progress,
  155. UpdateStatusToString(current_op),
  156. new_version.c_str(),
  157. new_size);
  158. return true;
  159. }
  160. int UpdateEngineClient::GetNeedReboot() {
  161. int64_t last_checked_time = 0;
  162. double progress = 0.0;
  163. UpdateStatus current_op;
  164. string new_version;
  165. int64_t new_size = 0;
  166. if (!client_->GetStatus(&last_checked_time,
  167. &progress,
  168. &current_op,
  169. &new_version,
  170. &new_size)) {
  171. return 1;
  172. }
  173. if (current_op == UpdateStatus::UPDATED_NEED_REBOOT) {
  174. return 0;
  175. }
  176. return 2;
  177. }
  178. class UpdateWaitHandler : public ExitingStatusUpdateHandler {
  179. public:
  180. explicit UpdateWaitHandler(bool exit_on_error,
  181. update_engine::UpdateEngineClient* client)
  182. : exit_on_error_(exit_on_error), client_(client) {}
  183. ~UpdateWaitHandler() override = default;
  184. void HandleStatusUpdate(int64_t last_checked_time,
  185. double progress,
  186. UpdateStatus current_operation,
  187. const string& new_version,
  188. int64_t new_size) override;
  189. private:
  190. bool exit_on_error_;
  191. update_engine::UpdateEngineClient* client_;
  192. };
  193. void UpdateWaitHandler::HandleStatusUpdate(int64_t /* last_checked_time */,
  194. double /* progress */,
  195. UpdateStatus current_operation,
  196. const string& /* new_version */,
  197. int64_t /* new_size */) {
  198. if (exit_on_error_ && current_operation == UpdateStatus::IDLE) {
  199. int last_attempt_error;
  200. ErrorCode code = ErrorCode::kSuccess;
  201. if (client_ && client_->GetLastAttemptError(&last_attempt_error))
  202. code = static_cast<ErrorCode>(last_attempt_error);
  203. LOG(ERROR) << "Update failed, current operation is "
  204. << UpdateStatusToString(current_operation)
  205. << ", last error code is " << ErrorCodeToString(code) << "("
  206. << last_attempt_error << ")";
  207. exit(1);
  208. }
  209. if (current_operation == UpdateStatus::UPDATED_NEED_REBOOT) {
  210. LOG(INFO) << "Update succeeded -- reboot needed.";
  211. exit(0);
  212. }
  213. }
  214. int UpdateEngineClient::ProcessFlags() {
  215. DEFINE_string(app_version, "", "Force the current app version.");
  216. DEFINE_string(channel,
  217. "",
  218. "Set the target channel. The device will be powerwashed if the "
  219. "target channel is more stable than the current channel unless "
  220. "--nopowerwash is specified.");
  221. DEFINE_bool(check_for_update, false, "Initiate check for updates.");
  222. DEFINE_string(
  223. cohort_hint, "", "Set the current cohort hint to the passed value.");
  224. DEFINE_bool(follow,
  225. false,
  226. "Wait for any update operations to complete."
  227. "Exit status is 0 if the update succeeded, and 1 otherwise.");
  228. DEFINE_bool(interactive, true, "Mark the update request as interactive.");
  229. DEFINE_string(omaha_url, "", "The URL of the Omaha update server.");
  230. DEFINE_string(p2p_update,
  231. "",
  232. "Enables (\"yes\") or disables (\"no\") the peer-to-peer update"
  233. " sharing.");
  234. DEFINE_bool(powerwash,
  235. true,
  236. "When performing rollback or channel change, "
  237. "do a powerwash or allow it respectively.");
  238. DEFINE_bool(reboot, false, "Initiate a reboot if needed.");
  239. DEFINE_bool(is_reboot_needed,
  240. false,
  241. "Exit status 0 if reboot is needed, "
  242. "2 if reboot is not needed or 1 if an error occurred.");
  243. DEFINE_bool(block_until_reboot_is_needed,
  244. false,
  245. "Blocks until reboot is "
  246. "needed. Returns non-zero exit status if an error occurred.");
  247. DEFINE_bool(reset_status, false, "Sets the status in update_engine to idle.");
  248. DEFINE_bool(rollback,
  249. false,
  250. "Perform a rollback to the previous partition. The device will "
  251. "be powerwashed unless --nopowerwash is specified.");
  252. DEFINE_bool(can_rollback,
  253. false,
  254. "Shows whether rollback partition "
  255. "is available.");
  256. DEFINE_bool(show_channel, false, "Show the current and target channels.");
  257. DEFINE_bool(show_cohort_hint, false, "Show the current cohort hint.");
  258. DEFINE_bool(show_p2p_update,
  259. false,
  260. "Show the current setting for peer-to-peer update sharing.");
  261. DEFINE_bool(show_update_over_cellular,
  262. false,
  263. "Show the current setting for updates over cellular networks.");
  264. DEFINE_bool(status, false, "Print the status to stdout.");
  265. DEFINE_bool(update,
  266. false,
  267. "Forces an update and waits for it to complete. "
  268. "Implies --follow.");
  269. DEFINE_string(update_over_cellular,
  270. "",
  271. "Enables (\"yes\") or disables (\"no\") the updates over "
  272. "cellular networks.");
  273. DEFINE_bool(watch_for_updates,
  274. false,
  275. "Listen for status updates and print them to the screen.");
  276. DEFINE_bool(prev_version,
  277. false,
  278. "Show the previous OS version used before the update reboot.");
  279. DEFINE_bool(last_attempt_error, false, "Show the last attempt error.");
  280. DEFINE_bool(eol_status, false, "Show the current end-of-life status.");
  281. DEFINE_bool(install, false, "Requests an install.");
  282. DEFINE_string(dlc_module_ids, "", "colon-separated list of DLC IDs.");
  283. // Boilerplate init commands.
  284. base::CommandLine::Init(argc_, argv_);
  285. brillo::FlagHelper::Init(argc_, argv_, "A/B Update Engine Client");
  286. // Ensure there are no positional arguments.
  287. const vector<string> positional_args =
  288. base::CommandLine::ForCurrentProcess()->GetArgs();
  289. if (!positional_args.empty()) {
  290. LOG(ERROR) << "Found a positional argument '" << positional_args.front()
  291. << "'. If you want to pass a value to a flag, pass it as "
  292. "--flag=value.";
  293. return 1;
  294. }
  295. // Update the status if requested.
  296. if (FLAGS_reset_status) {
  297. LOG(INFO) << "Setting Update Engine status to idle ...";
  298. if (client_->ResetStatus()) {
  299. LOG(INFO) << "ResetStatus succeeded; to undo partition table changes "
  300. "run:\n"
  301. "(D=$(rootdev -d) P=$(rootdev -s); cgpt p -i$(($(echo "
  302. "${P#$D} | sed 's/^[^0-9]*//')-1)) $D;)";
  303. } else {
  304. LOG(ERROR) << "ResetStatus failed";
  305. return 1;
  306. }
  307. }
  308. // Changes the current update over cellular network setting.
  309. if (!FLAGS_update_over_cellular.empty()) {
  310. bool allowed = FLAGS_update_over_cellular == "yes";
  311. if (!allowed && FLAGS_update_over_cellular != "no") {
  312. LOG(ERROR) << "Unknown option: \"" << FLAGS_update_over_cellular
  313. << "\". Please specify \"yes\" or \"no\".";
  314. } else {
  315. if (!client_->SetUpdateOverCellularPermission(allowed)) {
  316. LOG(ERROR) << "Error setting the update over cellular setting.";
  317. return 1;
  318. }
  319. }
  320. }
  321. // Show the current update over cellular network setting.
  322. if (FLAGS_show_update_over_cellular) {
  323. bool allowed;
  324. if (!client_->GetUpdateOverCellularPermission(&allowed)) {
  325. LOG(ERROR) << "Error getting the update over cellular setting.";
  326. return 1;
  327. }
  328. LOG(INFO) << "Current update over cellular network setting: "
  329. << (allowed ? "ENABLED" : "DISABLED");
  330. }
  331. // Change/show the cohort hint.
  332. bool set_cohort_hint =
  333. base::CommandLine::ForCurrentProcess()->HasSwitch("cohort_hint");
  334. if (set_cohort_hint) {
  335. LOG(INFO) << "Setting cohort hint to: \"" << FLAGS_cohort_hint << "\"";
  336. if (!client_->SetCohortHint(FLAGS_cohort_hint)) {
  337. LOG(ERROR) << "Error setting the cohort hint.";
  338. return 1;
  339. }
  340. }
  341. if (FLAGS_show_cohort_hint || set_cohort_hint) {
  342. string cohort_hint;
  343. if (!client_->GetCohortHint(&cohort_hint)) {
  344. LOG(ERROR) << "Error getting the cohort hint.";
  345. return 1;
  346. }
  347. LOG(INFO) << "Current cohort hint: \"" << cohort_hint << "\"";
  348. }
  349. if (!FLAGS_powerwash && !FLAGS_rollback && FLAGS_channel.empty()) {
  350. LOG(ERROR) << "powerwash flag only makes sense rollback or channel change";
  351. return 1;
  352. }
  353. // Change the P2P enabled setting.
  354. if (!FLAGS_p2p_update.empty()) {
  355. bool enabled = FLAGS_p2p_update == "yes";
  356. if (!enabled && FLAGS_p2p_update != "no") {
  357. LOG(ERROR) << "Unknown option: \"" << FLAGS_p2p_update
  358. << "\". Please specify \"yes\" or \"no\".";
  359. } else {
  360. if (!client_->SetP2PUpdatePermission(enabled)) {
  361. LOG(ERROR) << "Error setting the peer-to-peer update setting.";
  362. return 1;
  363. }
  364. }
  365. }
  366. // Show the rollback availability.
  367. if (FLAGS_can_rollback) {
  368. string rollback_partition;
  369. if (!client_->GetRollbackPartition(&rollback_partition)) {
  370. LOG(ERROR) << "Error while querying rollback partition availability.";
  371. return 1;
  372. }
  373. bool can_rollback = true;
  374. if (rollback_partition.empty()) {
  375. rollback_partition = "UNAVAILABLE";
  376. can_rollback = false;
  377. } else {
  378. rollback_partition = "AVAILABLE: " + rollback_partition;
  379. }
  380. LOG(INFO) << "Rollback partition: " << rollback_partition;
  381. if (!can_rollback) {
  382. return 1;
  383. }
  384. }
  385. // Show the current P2P enabled setting.
  386. if (FLAGS_show_p2p_update) {
  387. bool enabled;
  388. if (!client_->GetP2PUpdatePermission(&enabled)) {
  389. LOG(ERROR) << "Error getting the peer-to-peer update setting.";
  390. return 1;
  391. }
  392. LOG(INFO) << "Current update using P2P setting: "
  393. << (enabled ? "ENABLED" : "DISABLED");
  394. }
  395. // First, update the target channel if requested.
  396. if (!FLAGS_channel.empty()) {
  397. if (!client_->SetTargetChannel(FLAGS_channel, FLAGS_powerwash)) {
  398. LOG(ERROR) << "Error setting the channel.";
  399. return 1;
  400. }
  401. LOG(INFO) << "Channel permanently set to: " << FLAGS_channel;
  402. }
  403. // Show the current and target channels if requested.
  404. if (FLAGS_show_channel) {
  405. string current_channel;
  406. string target_channel;
  407. if (!client_->GetChannel(&current_channel)) {
  408. LOG(ERROR) << "Error getting the current channel.";
  409. return 1;
  410. }
  411. if (!client_->GetTargetChannel(&target_channel)) {
  412. LOG(ERROR) << "Error getting the target channel.";
  413. return 1;
  414. }
  415. LOG(INFO) << "Current Channel: " << current_channel;
  416. if (!target_channel.empty())
  417. LOG(INFO) << "Target Channel (pending update): " << target_channel;
  418. }
  419. bool do_update_request = FLAGS_check_for_update || FLAGS_update ||
  420. !FLAGS_app_version.empty() ||
  421. !FLAGS_omaha_url.empty();
  422. if (FLAGS_update)
  423. FLAGS_follow = true;
  424. if (do_update_request && FLAGS_rollback) {
  425. LOG(ERROR) << "Incompatible flags specified with rollback."
  426. << "Rollback should not include update-related flags.";
  427. return 1;
  428. }
  429. if (FLAGS_rollback) {
  430. LOG(INFO) << "Requesting rollback.";
  431. if (!client_->Rollback(FLAGS_powerwash)) {
  432. LOG(ERROR) << "Rollback request failed.";
  433. return 1;
  434. }
  435. }
  436. if (FLAGS_install) {
  437. // Parse DLC module IDs.
  438. vector<string> dlc_module_ids;
  439. if (!FLAGS_dlc_module_ids.empty()) {
  440. dlc_module_ids = base::SplitString(FLAGS_dlc_module_ids,
  441. ":",
  442. base::TRIM_WHITESPACE,
  443. base::SPLIT_WANT_ALL);
  444. }
  445. if (dlc_module_ids.empty()) {
  446. LOG(ERROR) << "dlc_module_ids is empty:" << FLAGS_dlc_module_ids;
  447. return 1;
  448. }
  449. if (!client_->AttemptInstall(FLAGS_omaha_url, dlc_module_ids)) {
  450. LOG(ERROR) << "AttemptInstall failed.";
  451. return 1;
  452. }
  453. return 0;
  454. } else if (!FLAGS_dlc_module_ids.empty()) {
  455. LOG(ERROR) << "dlc_module_ids is not empty while install is not set:"
  456. << FLAGS_dlc_module_ids;
  457. return 1;
  458. }
  459. // Initiate an update check, if necessary.
  460. if (do_update_request) {
  461. LOG_IF(WARNING, FLAGS_reboot) << "-reboot flag ignored.";
  462. string app_version = FLAGS_app_version;
  463. if (FLAGS_update && app_version.empty()) {
  464. app_version = "ForcedUpdate";
  465. LOG(INFO) << "Forcing an update by setting app_version to ForcedUpdate.";
  466. }
  467. LOG(INFO) << "Initiating update check and install.";
  468. if (!client_->AttemptUpdate(
  469. app_version, FLAGS_omaha_url, FLAGS_interactive)) {
  470. LOG(ERROR) << "Error checking for update.";
  471. return 1;
  472. }
  473. }
  474. // These final options are all mutually exclusive with one another.
  475. if (FLAGS_follow + FLAGS_watch_for_updates + FLAGS_reboot + FLAGS_status +
  476. FLAGS_is_reboot_needed + FLAGS_block_until_reboot_is_needed >
  477. 1) {
  478. LOG(ERROR) << "Multiple exclusive options selected. "
  479. << "Select only one of --follow, --watch_for_updates, --reboot, "
  480. << "--is_reboot_needed, --block_until_reboot_is_needed, "
  481. << "or --status.";
  482. return 1;
  483. }
  484. if (FLAGS_status) {
  485. LOG(INFO) << "Querying Update Engine status...";
  486. if (!ShowStatus()) {
  487. LOG(ERROR) << "Failed to query status";
  488. return 1;
  489. }
  490. return 0;
  491. }
  492. if (FLAGS_follow) {
  493. LOG(INFO) << "Waiting for update to complete.";
  494. auto handler = new UpdateWaitHandler(true, client_.get());
  495. handlers_.emplace_back(handler);
  496. client_->RegisterStatusUpdateHandler(handler);
  497. return kContinueRunning;
  498. }
  499. if (FLAGS_watch_for_updates) {
  500. LOG(INFO) << "Watching for status updates.";
  501. auto handler = new WatchingStatusUpdateHandler();
  502. handlers_.emplace_back(handler);
  503. client_->RegisterStatusUpdateHandler(handler);
  504. return kContinueRunning;
  505. }
  506. if (FLAGS_reboot) {
  507. LOG(INFO) << "Requesting a reboot...";
  508. client_->RebootIfNeeded();
  509. return 0;
  510. }
  511. if (FLAGS_prev_version) {
  512. string prev_version;
  513. if (!client_->GetPrevVersion(&prev_version)) {
  514. LOG(ERROR) << "Error getting previous version.";
  515. } else {
  516. LOG(INFO) << "Previous version = " << prev_version;
  517. }
  518. }
  519. if (FLAGS_is_reboot_needed) {
  520. int ret = GetNeedReboot();
  521. if (ret == 1) {
  522. LOG(ERROR) << "Could not query the current operation.";
  523. }
  524. return ret;
  525. }
  526. if (FLAGS_block_until_reboot_is_needed) {
  527. auto handler = new UpdateWaitHandler(false, nullptr);
  528. handlers_.emplace_back(handler);
  529. client_->RegisterStatusUpdateHandler(handler);
  530. return kContinueRunning;
  531. }
  532. if (FLAGS_last_attempt_error) {
  533. int last_attempt_error;
  534. if (!client_->GetLastAttemptError(&last_attempt_error)) {
  535. LOG(ERROR) << "Error getting last attempt error.";
  536. } else {
  537. ErrorCode code = static_cast<ErrorCode>(last_attempt_error);
  538. printf(
  539. "ERROR_CODE=%i\n"
  540. "ERROR_MESSAGE=%s\n",
  541. last_attempt_error,
  542. ErrorCodeToString(code).c_str());
  543. }
  544. }
  545. if (FLAGS_eol_status) {
  546. int eol_status;
  547. if (!client_->GetEolStatus(&eol_status)) {
  548. LOG(ERROR) << "Error getting the end-of-life status.";
  549. } else {
  550. EolStatus eol_status_code = static_cast<EolStatus>(eol_status);
  551. printf("EOL_STATUS=%s\n", EolStatusToString(eol_status_code));
  552. }
  553. }
  554. return 0;
  555. }
  556. void UpdateEngineClient::ProcessFlagsAndExit() {
  557. int ret = ProcessFlags();
  558. if (ret != kContinueRunning)
  559. QuitWithExitCode(ret);
  560. }
  561. } // namespace
  562. int main(int argc, char** argv) {
  563. UpdateEngineClient client(argc, argv);
  564. return client.Run();
  565. }