Parse and expose end-of-life flag.

Omaha update or noupdate response can include _key=value pairs with
arbitrary data. One of those key can be "_eol" with the one of the
values "supported", "security-only" or "eol" which notifies the device
the end-of-life status of the device with respect to updates. This
information is now exposed via GetEolStatus() to the client so it
can be properly displayed in the UI.

Bug: 27924505
TEST=Added unittest. Run `update_engine_client --eol_status` on link.

Change-Id: Icc15f25b4d0b19cc894f5afc52ac7c43c7818982
diff --git a/Android.mk b/Android.mk
index 107a844..93c6c87 100644
--- a/Android.mk
+++ b/Android.mk
@@ -315,6 +315,7 @@
     omaha_request_action.cc \
     omaha_request_params.cc \
     omaha_response_handler_action.cc \
+    omaha_utils.cc \
     p2p_manager.cc \
     payload_state.cc \
     proxy_resolver.cc \
@@ -524,7 +525,8 @@
     libupdate_engine_client
 LOCAL_SRC_FILES := \
     update_engine_client.cc \
-    common/error_code_utils.cc
+    common/error_code_utils.cc \
+    omaha_utils.cc
 else  # !defined(BRILLO)
 #TODO(deymo): Remove external/cros/system_api/dbus once the strings are moved
 # out of the DBus interface.
@@ -904,6 +906,7 @@
     omaha_request_action_unittest.cc \
     omaha_request_params_unittest.cc \
     omaha_response_handler_action_unittest.cc \
+    omaha_utils_unittest.cc \
     p2p_manager_unittest.cc \
     payload_consumer/bzip_extent_writer_unittest.cc \
     payload_consumer/delta_performer_integration_test.cc \
diff --git a/UpdateEngine.conf b/UpdateEngine.conf
index 9cf6042..9238e3a 100644
--- a/UpdateEngine.conf
+++ b/UpdateEngine.conf
@@ -57,6 +57,9 @@
     <allow send_destination="org.chromium.UpdateEngine"
            send_interface="org.chromium.UpdateEngineInterface"
            send_member="GetLastAttemptError"/>
+    <allow send_destination="org.chromium.UpdateEngine"
+           send_interface="org.chromium.UpdateEngineInterface"
+           send_member="GetEolStatus"/>
     <allow send_interface="org.chromium.UpdateEngineLibcrosProxyResolvedInterface" />
   </policy>
   <policy user="power">
diff --git a/binder_bindings/android/brillo/IUpdateEngine.aidl b/binder_bindings/android/brillo/IUpdateEngine.aidl
index 1c0a3e5..6a3295a 100644
--- a/binder_bindings/android/brillo/IUpdateEngine.aidl
+++ b/binder_bindings/android/brillo/IUpdateEngine.aidl
@@ -37,4 +37,5 @@
   String GetRollbackPartition();
   void RegisterStatusCallback(in IUpdateEngineStatusCallback callback);
   int GetLastAttemptError();
+  int GetEolStatus();
 }
diff --git a/binder_service_brillo.cc b/binder_service_brillo.cc
index 6a6a16e..3947ae1 100644
--- a/binder_service_brillo.cc
+++ b/binder_service_brillo.cc
@@ -192,12 +192,15 @@
                            out_last_attempt_error);
 }
 
+Status BinderUpdateEngineBrilloService::GetEolStatus(int* out_eol_status) {
+  return CallCommonHandler(&UpdateEngineService::GetEolStatus, out_eol_status);
+}
+
 void BinderUpdateEngineBrilloService::UnregisterStatusCallback(
     IUpdateEngineStatusCallback* callback) {
   auto it = callbacks_.begin();
-
-  for (; it != callbacks_.end() && it->get() != callback; it++)
-    ;
+  while (it != callbacks_.end() && it->get() != callback)
+    it++;
 
   if (it == callbacks_.end()) {
     LOG(ERROR) << "Got death notification for unknown callback.";
diff --git a/binder_service_brillo.h b/binder_service_brillo.h
index 497b1b0..b3bb81f 100644
--- a/binder_service_brillo.h
+++ b/binder_service_brillo.h
@@ -86,6 +86,7 @@
       override;
   android::binder::Status GetLastAttemptError(
       int* out_last_attempt_error) override;
+  android::binder::Status GetEolStatus(int* out_eol_status) override;
 
  private:
   // Generic function for dispatching to the common service.
diff --git a/client_library/client_binder.cc b/client_library/client_binder.cc
index 321dfc4..6a61722 100644
--- a/client_library/client_binder.cc
+++ b/client_library/client_binder.cc
@@ -28,8 +28,8 @@
 using android::OK;
 using android::String16;
 using android::String8;
-using android::brillo::ParcelableUpdateEngineStatus;
 using android::binder::Status;
+using android::brillo::ParcelableUpdateEngineStatus;
 using android::getService;
 using chromeos_update_engine::StringToUpdateStatus;
 using chromeos_update_engine::UpdateEngineService;
@@ -177,10 +177,7 @@
 
 bool BinderUpdateEngineClient::UnregisterStatusUpdateHandler(
     StatusUpdateHandler* handler) {
-  auto it = handlers_.begin();
-
-  for (; *it != handler && it != handlers_.end(); it++);
-
+  auto it = std::find(handlers_.begin(), handlers_.end(), handler);
   if (it != handlers_.end()) {
     handlers_.erase(it);
     return true;
@@ -226,5 +223,15 @@
   return true;
 }
 
+bool BinderUpdateEngineClient::GetEolStatus(int32_t* eol_status) const {
+  int out_as_int;
+
+  if (!service_->GetEolStatus(&out_as_int).isOk())
+    return false;
+
+  *eol_status = out_as_int;
+  return true;
+}
+
 }  // namespace internal
 }  // namespace update_engine
diff --git a/client_library/client_binder.h b/client_library/client_binder.h
index 72f80dd..cd857e0 100644
--- a/client_library/client_binder.h
+++ b/client_library/client_binder.h
@@ -23,14 +23,13 @@
 #include <vector>
 
 #include <base/macros.h>
-#include <utils/StrongPointer.h>
 #include <utils/String16.h>
+#include <utils/StrongPointer.h>
 
 #include <brillo/binder_watcher.h>
 
-#include "android/brillo/IUpdateEngine.h"
 #include "android/brillo/BnUpdateEngineStatusCallback.h"
-
+#include "android/brillo/IUpdateEngine.h"
 
 #include "update_engine/client_library/include/update_engine/client.h"
 
@@ -82,11 +81,14 @@
 
   bool GetLastAttemptError(int32_t* last_attempt_error) const override;
 
+  bool GetEolStatus(int32_t* eol_status) const override;
+
  private:
   class StatusUpdateCallback :
       public android::brillo::BnUpdateEngineStatusCallback {
    public:
-    StatusUpdateCallback(BinderUpdateEngineClient* client) : client_(client) {}
+    explicit StatusUpdateCallback(BinderUpdateEngineClient* client)
+        : client_(client) {}
 
     android::binder::Status HandleStatusUpdate(
         int64_t last_checked_time,
diff --git a/client_library/client_dbus.cc b/client_library/client_dbus.cc
index 0d6b783..5cb63a4 100644
--- a/client_library/client_dbus.cc
+++ b/client_library/client_dbus.cc
@@ -171,10 +171,7 @@
 
 bool DBusUpdateEngineClient::UnregisterStatusUpdateHandler(
     StatusUpdateHandler* handler) {
-  auto it = handlers_.begin();
-
-  for (; *it != handler && it != handlers_.end(); it++);
-
+  auto it = std::find(handlers_.begin(), handlers_.end(), handler);
   if (it != handlers_.end()) {
     handlers_.erase(it);
     return true;
@@ -230,5 +227,9 @@
   return proxy_->GetLastAttemptError(last_attempt_error, nullptr);
 }
 
+bool DBusUpdateEngineClient::GetEolStatus(int32_t* eol_status) const {
+  return proxy_->GetEolStatus(eol_status, nullptr);
+}
+
 }  // namespace internal
 }  // namespace update_engine
diff --git a/client_library/client_dbus.h b/client_library/client_dbus.h
index 02a7e84..a2de594 100644
--- a/client_library/client_dbus.h
+++ b/client_library/client_dbus.h
@@ -75,6 +75,8 @@
 
   bool GetLastAttemptError(int32_t* last_attempt_error) const override;
 
+  bool GetEolStatus(int32_t* eol_status) const override;
+
  private:
   void DBusStatusHandlersRegistered(const std::string& interface,
                                     const std::string& signal_name,
diff --git a/client_library/include/update_engine/client.h b/client_library/include/update_engine/client.h
index 62ac5fb..7956dbd 100644
--- a/client_library/include/update_engine/client.h
+++ b/client_library/include/update_engine/client.h
@@ -115,6 +115,9 @@
   // Get the last UpdateAttempt error code.
   virtual bool GetLastAttemptError(int32_t* last_attempt_error) const = 0;
 
+  // Get the current end-of-life status code. See EolStatus enum for details.
+  virtual bool GetEolStatus(int32_t* eol_status) const = 0;
+
  protected:
   // Use CreateInstance().
   UpdateEngineClient() = default;
diff --git a/common/constants.cc b/common/constants.cc
index f138ce3..c0bb874 100644
--- a/common/constants.cc
+++ b/common/constants.cc
@@ -57,6 +57,7 @@
 const char kPrefsOmahaCohort[] = "omaha-cohort";
 const char kPrefsOmahaCohortHint[] = "omaha-cohort-hint";
 const char kPrefsOmahaCohortName[] = "omaha-cohort-name";
+const char kPrefsOmahaEolStatus[] = "omaha-eol-status";
 const char kPrefsP2PEnabled[] = "p2p-enabled";
 const char kPrefsP2PFirstAttemptTimestamp[] = "p2p-first-attempt-timestamp";
 const char kPrefsP2PNumAttempts[] = "p2p-num-attempts";
diff --git a/common/constants.h b/common/constants.h
index f0d589d..660d9a9 100644
--- a/common/constants.h
+++ b/common/constants.h
@@ -61,6 +61,7 @@
 extern const char kPrefsOmahaCohort[];
 extern const char kPrefsOmahaCohortHint[];
 extern const char kPrefsOmahaCohortName[];
+extern const char kPrefsOmahaEolStatus[];
 extern const char kPrefsP2PEnabled[];
 extern const char kPrefsP2PFirstAttemptTimestamp[];
 extern const char kPrefsP2PNumAttempts[];
diff --git a/common_service.cc b/common_service.cc
index f0b818f..ade5349 100644
--- a/common_service.cc
+++ b/common_service.cc
@@ -33,9 +33,10 @@
 #include "update_engine/common/utils.h"
 #include "update_engine/connection_manager_interface.h"
 #include "update_engine/omaha_request_params.h"
+#include "update_engine/omaha_utils.h"
 #include "update_engine/p2p_manager.h"
-#include "update_engine/update_attempter.h"
 #include "update_engine/payload_state_interface.h"
+#include "update_engine/update_attempter.h"
 
 using base::StringPrintf;
 using brillo::ErrorPtr;
@@ -325,4 +326,21 @@
   *out_last_attempt_error = static_cast<int>(error_code);
   return true;
 }
+
+bool UpdateEngineService::GetEolStatus(ErrorPtr* error,
+                                       int32_t* out_eol_status) {
+  PrefsInterface* prefs = system_state_->prefs();
+
+  string str_eol_status;
+  if (prefs->Exists(kPrefsOmahaEolStatus) &&
+      !prefs->GetString(kPrefsOmahaEolStatus, &str_eol_status)) {
+    LogAndSetError(error, FROM_HERE, "Error getting the end-of-life status.");
+    return false;
+  }
+
+  // StringToEolStatus will return kSupported for invalid values.
+  *out_eol_status = static_cast<int32_t>(StringToEolStatus(str_eol_status));
+  return true;
+}
+
 }  // namespace chromeos_update_engine
diff --git a/common_service.h b/common_service.h
index 4ad8862..1d380bc 100644
--- a/common_service.h
+++ b/common_service.h
@@ -131,6 +131,10 @@
   bool GetLastAttemptError(brillo::ErrorPtr* error,
                            int32_t* out_last_attempt_error);
 
+  // Returns the current end-of-life status of the device. This value is updated
+  // on every update check and persisted on disk across reboots.
+  bool GetEolStatus(brillo::ErrorPtr* error, int32_t* out_eol_status);
+
  private:
   SystemState* system_state_;
 };
diff --git a/common_service_unittest.cc b/common_service_unittest.cc
index 1c144d1..0a7bfc3 100644
--- a/common_service_unittest.cc
+++ b/common_service_unittest.cc
@@ -23,7 +23,9 @@
 #include <policy/libpolicy.h>
 #include <policy/mock_device_policy.h>
 
+#include "update_engine/common/fake_prefs.h"
 #include "update_engine/fake_system_state.h"
+#include "update_engine/omaha_utils.h"
 
 using std::string;
 using testing::Return;
@@ -131,4 +133,19 @@
                                UpdateEngineService::kErrorFailed));
 }
 
+TEST_F(UpdateEngineServiceTest, GetEolStatusTest) {
+  FakePrefs fake_prefs;
+  fake_system_state_.set_prefs(&fake_prefs);
+  // The default value should be "supported".
+  int32_t eol_status = static_cast<int32_t>(EolStatus::kEol);
+  EXPECT_TRUE(common_service_.GetEolStatus(&error_, &eol_status));
+  EXPECT_EQ(nullptr, error_);
+  EXPECT_EQ(EolStatus::kSupported, static_cast<EolStatus>(eol_status));
+
+  fake_prefs.SetString(kPrefsOmahaEolStatus, "security-only");
+  EXPECT_TRUE(common_service_.GetEolStatus(&error_, &eol_status));
+  EXPECT_EQ(nullptr, error_);
+  EXPECT_EQ(EolStatus::kSecurityOnly, static_cast<EolStatus>(eol_status));
+}
+
 }  // namespace chromeos_update_engine
diff --git a/dbus_bindings/org.chromium.UpdateEngineInterface.dbus-xml b/dbus_bindings/org.chromium.UpdateEngineInterface.dbus-xml
index 02287de..aa99508 100644
--- a/dbus_bindings/org.chromium.UpdateEngineInterface.dbus-xml
+++ b/dbus_bindings/org.chromium.UpdateEngineInterface.dbus-xml
@@ -83,5 +83,8 @@
     <method name="GetLastAttemptError">
       <arg type="i" name="last_attempt_error" direction="out" />
     </method>
+    <method name="GetEolStatus">
+      <arg type="i" name="eol_status" direction="out" />
+    </method>
   </interface>
 </node>
diff --git a/dbus_service.cc b/dbus_service.cc
index 392555f..de1f9b5 100644
--- a/dbus_service.cc
+++ b/dbus_service.cc
@@ -133,8 +133,13 @@
 }
 
 bool DBusUpdateEngineService::GetLastAttemptError(
-    ErrorPtr* error, int32_t* out_last_attempt_error){
- return common_->GetLastAttemptError(error, out_last_attempt_error);
+    ErrorPtr* error, int32_t* out_last_attempt_error) {
+  return common_->GetLastAttemptError(error, out_last_attempt_error);
+}
+
+bool DBusUpdateEngineService::GetEolStatus(ErrorPtr* error,
+                                           int32_t* out_eol_status) {
+  return common_->GetEolStatus(error, out_eol_status);
 }
 
 UpdateEngineAdaptor::UpdateEngineAdaptor(SystemState* system_state,
diff --git a/dbus_service.h b/dbus_service.h
index 1486e3c..12f8cf1 100644
--- a/dbus_service.h
+++ b/dbus_service.h
@@ -133,6 +133,10 @@
   // ErrorCode will be returned.
   bool GetLastAttemptError(brillo::ErrorPtr* error,
                            int32_t* out_last_attempt_error) override;
+
+  // Returns the current end-of-life status of the device in |out_eol_status|.
+  bool GetEolStatus(brillo::ErrorPtr* error, int32_t* out_eol_status) override;
+
  private:
   std::unique_ptr<UpdateEngineService> common_;
 };
diff --git a/omaha_request_action.cc b/omaha_request_action.cc
index 018f9aa..16338a8 100644
--- a/omaha_request_action.cc
+++ b/omaha_request_action.cc
@@ -77,6 +77,9 @@
 
 static const char* kOmahaUpdaterVersion = "0.1.0.0";
 
+// updatecheck attributes (without the underscore prefix).
+static const char* kEolAttr = "eol";
+
 namespace {
 
 // Returns an XML ping element attribute assignment with attribute
@@ -339,6 +342,7 @@
   bool app_cohortname_set = false;
   string updatecheck_status;
   string updatecheck_poll_interval;
+  map<string, string> updatecheck_attrs;
   string daystart_elapsed_days;
   string daystart_elapsed_seconds;
   vector<string> url_codebase;
@@ -386,6 +390,12 @@
     // There is only supposed to be a single <updatecheck> element.
     data->updatecheck_status = attrs["status"];
     data->updatecheck_poll_interval = attrs["PollInterval"];
+    // Omaha sends arbitrary key-value pairs as extra attributes starting with
+    // an underscore.
+    for (const auto& attr : attrs) {
+      if (!attr.first.empty() && attr.first[0] == '_')
+        data->updatecheck_attrs[attr.first.substr(1)] = attr.second;
+    }
   } else if (data->current_path == "/response/daystart") {
     // Get the install-date.
     data->daystart_elapsed_days = attrs["elapsed_days"];
@@ -748,6 +758,9 @@
   if (parser_data->app_cohortname_set)
     PersistCohortData(kPrefsOmahaCohortName, parser_data->app_cohortname);
 
+  // Parse the updatecheck attributes.
+  PersistEolStatus(parser_data->updatecheck_attrs);
+
   if (!ParseStatus(parser_data, output_object, completer))
     return false;
 
@@ -1384,6 +1397,17 @@
   return true;
 }
 
+bool OmahaRequestAction::PersistEolStatus(const map<string, string>& attrs) {
+  auto eol_attr = attrs.find(kEolAttr);
+  if (eol_attr != attrs.end()) {
+    return system_state_->prefs()->SetString(kPrefsOmahaEolStatus,
+                                             eol_attr->second);
+  } else if (system_state_->prefs()->Exists(kPrefsOmahaEolStatus)) {
+    return system_state_->prefs()->Delete(kPrefsOmahaEolStatus);
+  }
+  return true;
+}
+
 void OmahaRequestAction::ActionCompleted(ErrorCode code) {
   // We only want to report this on "update check".
   if (ping_only_ || event_ != nullptr)
diff --git a/omaha_request_action.h b/omaha_request_action.h
index 1aeaf8a..2915a6a 100644
--- a/omaha_request_action.h
+++ b/omaha_request_action.h
@@ -21,6 +21,7 @@
 #include <sys/stat.h>
 #include <sys/types.h>
 
+#include <map>
 #include <memory>
 #include <string>
 #include <vector>
@@ -219,6 +220,10 @@
   bool PersistCohortData(const std::string& prefs_key,
                          const std::string& new_value);
 
+  // Parse and persist the end-of-life status flag sent back in the updatecheck
+  // tag attributes. The flag will be validated and stored in the Prefs.
+  bool PersistEolStatus(const std::map<std::string, std::string>& attrs);
+
   // If this is an update check request, initializes
   // |ping_active_days_| and |ping_roll_call_days_| to values that may
   // be sent as pings to Omaha.
diff --git a/omaha_request_action_unittest.cc b/omaha_request_action_unittest.cc
index 496143e..a7abdec 100644
--- a/omaha_request_action_unittest.cc
+++ b/omaha_request_action_unittest.cc
@@ -51,8 +51,6 @@
 
 using base::Time;
 using base::TimeDelta;
-using chromeos_update_engine::test_utils::System;
-using chromeos_update_engine::test_utils::WriteFileString;
 using std::string;
 using std::vector;
 using testing::AllOf;
@@ -355,9 +353,7 @@
              ? 0 : 1);
 
   loop.PostTask(base::Bind([&processor] { processor.StartProcessing(); }));
-  LOG(INFO) << "loop.PendingTasks() = " << loop.PendingTasks();
   loop.Run();
-  LOG(INFO) << "loop.PendingTasks() = " << loop.PendingTasks();
   EXPECT_FALSE(loop.PendingTasks());
   if (collector_action.has_input_object_ && out_response)
     *out_response = collector_action.omaha_response_;
@@ -1676,6 +1672,32 @@
                       nullptr));
 }
 
+TEST_F(OmahaRequestActionTest, ParseUpdateCheckAttributesTest) {
+  // Test that the "eol" flags is only parsed from the "_eol" attribute and not
+  // the "eol" attribute.
+  ASSERT_TRUE(
+      TestUpdateCheck(nullptr,  // request_params
+                      "<?xml version=\"1.0\" encoding=\"UTF-8\"?><response "
+                      "protocol=\"3.0\"><app appid=\"foo\" status=\"ok\">"
+                      "<ping status=\"ok\"/><updatecheck status=\"noupdate\" "
+                      "_eol=\"security-only\" eol=\"eol\" _foo=\"bar\"/>"
+                      "</app></response>",
+                      -1,
+                      false,  // ping_only
+                      ErrorCode::kSuccess,
+                      metrics::CheckResult::kNoUpdateAvailable,
+                      metrics::CheckReaction::kUnset,
+                      metrics::DownloadErrorCode::kUnset,
+                      nullptr,
+                      nullptr));
+  string eol_pref;
+  EXPECT_TRUE(
+      fake_system_state_.prefs()->GetString(kPrefsOmahaEolStatus, &eol_pref));
+  // Note that the eol="eol" attribute should be ignored and the _eol should be
+  // used instead.
+  EXPECT_EQ("security-only", eol_pref);
+}
+
 TEST_F(OmahaRequestActionTest, NoUniqueIDTest) {
   brillo::Blob post_data;
   ASSERT_FALSE(TestUpdateCheck(nullptr,  // request_params
diff --git a/omaha_utils.cc b/omaha_utils.cc
new file mode 100644
index 0000000..6bd7525
--- /dev/null
+++ b/omaha_utils.cc
@@ -0,0 +1,57 @@
+//
+// Copyright (C) 2016 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 "update_engine/omaha_utils.h"
+
+#include <base/logging.h>
+
+namespace chromeos_update_engine {
+
+namespace {
+
+// The possible string values for the end-of-life status.
+const char kEolStatusSupported[] = "supported";
+const char kEolStatusSecurityOnly[] = "security-only";
+const char kEolStatusEol[] = "eol";
+
+}  // namespace
+
+const char* EolStatusToString(EolStatus eol_status) {
+  switch (eol_status) {
+    case EolStatus::kSupported:
+      return kEolStatusSupported;
+    case EolStatus::kSecurityOnly:
+      return kEolStatusSecurityOnly;
+    case EolStatus::kEol:
+      return kEolStatusEol;
+  }
+  // Only reached if an invalid number is casted to |EolStatus|.
+  LOG(WARNING) << "Invalid EolStatus value: " << static_cast<int>(eol_status);
+  return kEolStatusSupported;
+}
+
+EolStatus StringToEolStatus(const std::string& eol_status) {
+  if (eol_status == kEolStatusSupported || eol_status.empty())
+    return EolStatus::kSupported;
+  if (eol_status == kEolStatusSecurityOnly)
+    return EolStatus::kSecurityOnly;
+  if (eol_status == kEolStatusEol)
+    return EolStatus::kEol;
+  LOG(WARNING) << "Invalid end-of-life attribute: " << eol_status;
+  return EolStatus::kSupported;
+}
+
+}  // namespace chromeos_update_engine
diff --git a/omaha_utils.h b/omaha_utils.h
new file mode 100644
index 0000000..8614540
--- /dev/null
+++ b/omaha_utils.h
@@ -0,0 +1,40 @@
+//
+// Copyright (C) 2016 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.
+//
+
+#ifndef UPDATE_ENGINE_OMAHA_UTILS_H_
+#define UPDATE_ENGINE_OMAHA_UTILS_H_
+
+#include <string>
+
+namespace chromeos_update_engine {
+
+// The end-of-life status of the device.
+enum class EolStatus {
+  kSupported = 0,
+  kSecurityOnly,
+  kEol,
+};
+
+// Returns the string representation of the |eol_status|.
+const char* EolStatusToString(EolStatus eol_status);
+
+// Converts the end-of-life status string to an EolStatus numeric value. In case
+// of an invalid string, the default "supported" value will be used instead.
+EolStatus StringToEolStatus(const std::string& eol_status);
+
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_OMAHA_UTILS_H_
diff --git a/omaha_utils_unittest.cc b/omaha_utils_unittest.cc
new file mode 100644
index 0000000..8ceb76b
--- /dev/null
+++ b/omaha_utils_unittest.cc
@@ -0,0 +1,42 @@
+//
+// Copyright (C) 2016 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 "update_engine/omaha_utils.h"
+
+#include <gtest/gtest.h>
+#include <vector>
+
+namespace chromeos_update_engine {
+
+class OmahaUtilsTest : public ::testing::Test {};
+
+TEST(OmahaUtilsTest, EolStatusTest) {
+  EXPECT_EQ(EolStatus::kEol, StringToEolStatus("eol"));
+
+  // Supported values are converted back and forth properly.
+  const std::vector<EolStatus> tests = {
+      EolStatus::kSupported, EolStatus::kSecurityOnly, EolStatus::kEol};
+  for (EolStatus eol_status : tests) {
+    EXPECT_EQ(eol_status, StringToEolStatus(EolStatusToString(eol_status)))
+        << "The StringToEolStatus() was " << EolStatusToString(eol_status);
+  }
+
+  // Invalid values are assumed as "supported".
+  EXPECT_EQ(EolStatus::kSupported, StringToEolStatus(""));
+  EXPECT_EQ(EolStatus::kSupported, StringToEolStatus("hello, world!"));
+}
+
+}  // namespace chromeos_update_engine
diff --git a/update_engine.gyp b/update_engine.gyp
index 5f24a83..b8eccdc 100644
--- a/update_engine.gyp
+++ b/update_engine.gyp
@@ -261,6 +261,7 @@
         'omaha_request_action.cc',
         'omaha_request_params.cc',
         'omaha_response_handler_action.cc',
+        'omaha_utils.cc',
         'p2p_manager.cc',
         'payload_state.cc',
         'proxy_resolver.cc',
@@ -344,6 +345,7 @@
       ],
       'sources': [
         'common/error_code_utils.cc',
+        'omaha_utils.cc',
         'update_engine_client.cc',
      ],
     },
@@ -511,6 +513,7 @@
             'omaha_request_action_unittest.cc',
             'omaha_request_params_unittest.cc',
             'omaha_response_handler_action_unittest.cc',
+            'omaha_utils_unittest.cc',
             'p2p_manager_unittest.cc',
             'payload_consumer/bzip_extent_writer_unittest.cc',
             'payload_consumer/delta_performer_integration_test.cc',
diff --git a/update_engine_client.cc b/update_engine_client.cc
index 6ade12c..55d7e64 100644
--- a/update_engine_client.cc
+++ b/update_engine_client.cc
@@ -32,10 +32,12 @@
 #include "update_engine/client.h"
 #include "update_engine/common/error_code.h"
 #include "update_engine/common/error_code_utils.h"
+#include "update_engine/omaha_utils.h"
 #include "update_engine/status_update_handler.h"
 #include "update_engine/update_status.h"
 #include "update_engine/update_status_utils.h"
 
+using chromeos_update_engine::EolStatus;
 using chromeos_update_engine::ErrorCode;
 using chromeos_update_engine::UpdateStatusToString;
 using chromeos_update_engine::utils::ErrorCodeToString;
@@ -273,6 +275,7 @@
   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_);
@@ -534,6 +537,16 @@
     }
   }
 
+  if (FLAGS_eol_status) {
+    int eol_status;
+    if (!client_->GetEolStatus(&eol_status)) {
+      LOG(ERROR) << "Error getting the end-of-life status.";
+    } else {
+      EolStatus eol_status_code = static_cast<EolStatus>(eol_status);
+      printf("EOL_STATUS=%s\n", EolStatusToString(eol_status_code));
+    }
+  }
+
   return 0;
 }