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