update_engine: Add PayloadState Exclusion Logic

|PayloadState| will exclude payloads based on specific update failures.
This is to prevent critical platform updates from being blocked by less
critical updates (e.g. DLCs). A layer of robustness is added in
protecting CrOS devices from falling off the update train.

Some important changes to mention:
 - Only during updates will update_engine exclude non-critical payloads
 - |OmahaRequestAction|, the current precursor |Action| to
 |OmahaResponseHandlerAction|, during a update will exclude
 faulty/excluded payloads prior to setting the |OmahaResponse| as an
 output object for suqsequent bonded |Action| to consume
 - When all payloads are excluded for an update, the |ErrorCode| will
 be indicated as |OmahaResponseInvalid| as this case is not a valid
 Omaha response update_engine should ever run into because non-critical
 updates must tag alongside a critical update

BUG=chromium:928805
TEST=FEATURES=test emerge-$B update_engine update_engine-client

Change-Id: I0551a228d0b84defb4d59966e8ed46a5d9278d60
Reviewed-on: https://chromium-review.googlesource.com/c/aosp/platform/system/update_engine/+/2190237
Tested-by: Jae Hoon Kim <kimjae@chromium.org>
Auto-Submit: Jae Hoon Kim <kimjae@chromium.org>
Reviewed-by: Amin Hassani <ahassani@chromium.org>
Commit-Queue: Jae Hoon Kim <kimjae@chromium.org>
diff --git a/omaha_request_action.cc b/omaha_request_action.cc
index 86d4b93..3a0b91c 100644
--- a/omaha_request_action.cc
+++ b/omaha_request_action.cc
@@ -55,6 +55,7 @@
 #include "update_engine/omaha_request_params.h"
 #include "update_engine/p2p_manager.h"
 #include "update_engine/payload_state_interface.h"
+#include "update_engine/update_attempter.h"
 
 using base::Optional;
 using base::Time;
@@ -534,6 +535,7 @@
 // False otherwise, in which case it sets any error code using |completer|.
 bool ParsePackage(OmahaParserData::App* app,
                   OmahaResponse* output_object,
+                  bool can_exclude,
                   ScopedActionCompleter* completer) {
   if (app->updatecheck_status.empty() ||
       app->updatecheck_status == kValNoUpdate) {
@@ -580,6 +582,7 @@
     LOG(INFO) << "Found package " << package.name;
 
     OmahaResponse::Package out_package;
+    out_package.can_exclude = can_exclude;
     for (const string& codebase : app->url_codebase) {
       if (codebase.empty()) {
         LOG(ERROR) << "Omaha Response URL has empty codebase";
@@ -625,6 +628,42 @@
   return true;
 }
 
+// Removes the candidate URLs which are excluded within packages, if all the
+// candidate URLs are excluded within a package, the package will be excluded.
+void ProcessExclusions(OmahaResponse* output_object,
+                       ExcluderInterface* excluder) {
+  for (auto package_it = output_object->packages.begin();
+       package_it != output_object->packages.end();
+       /* Increment logic in loop */) {
+    // If package cannot be excluded, quickly continue.
+    if (!package_it->can_exclude) {
+      ++package_it;
+      continue;
+    }
+    // Remove the excluded payload URLs.
+    for (auto payload_url_it = package_it->payload_urls.begin();
+         payload_url_it != package_it->payload_urls.end();
+         /* Increment logic in loop */) {
+      auto exclusion_name = utils::GetExclusionName(*payload_url_it);
+      // If payload URL is not excluded, quickly continue.
+      if (!excluder->IsExcluded(exclusion_name)) {
+        ++payload_url_it;
+        continue;
+      }
+      LOG(INFO) << "Excluding payload URL=" << *payload_url_it
+                << " for payload hash=" << package_it->hash;
+      payload_url_it = package_it->payload_urls.erase(payload_url_it);
+    }
+    // If there are no candidate payload URLs, remove the package.
+    if (package_it->payload_urls.empty()) {
+      LOG(INFO) << "Excluding payload hash=" << package_it->hash;
+      package_it = output_object->packages.erase(package_it);
+      continue;
+    }
+    ++package_it;
+  }
+}
+
 // Parses the 2 key version strings kernel_version and firmware_version. If the
 // field is not present, or cannot be parsed the values default to 0xffff.
 void ParseRollbackVersions(int allowed_milestones,
@@ -751,9 +790,15 @@
 
   // Package has to be parsed after Params now because ParseParams need to make
   // sure that postinstall action exists.
-  for (auto& app : parser_data->apps)
-    if (!ParsePackage(&app, output_object, completer))
+  for (auto& app : parser_data->apps) {
+    // Only allow exclusions for a non-critical package during an update. For
+    // non-critical package installations, let the errors propagate instead
+    // of being handled inside update_engine as installations are a dlcservice
+    // specific feature.
+    bool can_exclude = !params_->is_install() && params_->IsDlcAppId(app.id);
+    if (!ParsePackage(&app, output_object, can_exclude, completer))
       return false;
+  }
 
   return true;
 }
@@ -977,6 +1022,8 @@
   OmahaResponse output_object;
   if (!ParseResponse(&parser_data, &output_object, &completer))
     return;
+  ProcessExclusions(&output_object,
+                    system_state_->update_attempter()->GetExcluder());
   output_object.update_exists = true;
   SetOutputObject(output_object);
 
@@ -1469,6 +1516,14 @@
     return true;
   }
 
+  // Currently non-critical updates always update alongside the platform update
+  // (a critical update) so this case should never actually be hit if the
+  // request to Omaha for updates are correct. In other words, stop the update
+  // from happening as there are no packages in the response to process.
+  if (response.packages.empty()) {
+    LOG(ERROR) << "All packages were excluded.";
+  }
+
   // Note: We could technically delete the UpdateFirstSeenAt state when we
   // return true. If we do, it'll mean a device has to restart the
   // UpdateFirstSeenAt and thus help scattering take effect when the AU is