update_engine: Process Omaha response for rollback images
Omaha returns whether the image returned is a rollback in the
_rollback="true" argument. If this is set, the client has to check
whether it's OK to apply the rollback image (policy is specifically
requesting a rollback and verified boot will accept the image based
on its kernel and firmware key versions).
In addition to this, the device has to do a safe powerwash if the
image is a rollback. (We're not supporting rollbacks with partial
or no powerwash yet.)
We're also setting the rollback_happened preference to avoid force
updates happening before the policy is available again.
Chromium CL adding the error code: http://crrev.com/c/1047866
BUG=chromium:840432
TEST='cros_run_unit_tests --board=caroline --packages update_engine'
Change-Id: I1436ca96211b2a8523e78bf83602ef8b6b525570
Reviewed-on: https://chromium-review.googlesource.com/1047610
Commit-Ready: Marton Hunyady <hunyadym@chromium.org>
Tested-by: Marton Hunyady <hunyadym@chromium.org>
Reviewed-by: Amin Hassani <ahassani@chromium.org>
diff --git a/common/error_code.h b/common/error_code.h
index 9e7c71d..c301155 100644
--- a/common/error_code.h
+++ b/common/error_code.h
@@ -77,10 +77,11 @@
// kPayloadTimestampError = 51,
kUpdatedButNotActive = 52,
kNoUpdate = 53,
+ kRollbackNotPossible = 54,
// VERY IMPORTANT! When adding new error codes:
//
- // 1) Update tools/metrics/histograms/histograms.xml in Chrome.
+ // 1) Update tools/metrics/histograms/enums.xml in Chrome.
//
// 2) Update the assorted switch statements in update_engine which won't
// build until this case is added.
diff --git a/common/error_code_utils.cc b/common/error_code_utils.cc
index 6b72eee..a0e75f0 100644
--- a/common/error_code_utils.cc
+++ b/common/error_code_utils.cc
@@ -150,6 +150,8 @@
return "ErrorCode::kUpdatedButNotActive";
case ErrorCode::kNoUpdate:
return "ErrorCode::kNoUpdate";
+ case ErrorCode::kRollbackNotPossible:
+ return "ErrorCode::kRollbackNotPossible";
// Don't add a default case to let the compiler warn about newly added
// error codes which should be added here.
}
diff --git a/metrics_utils.cc b/metrics_utils.cc
index 0ff4cc9..c84aa8f 100644
--- a/metrics_utils.cc
+++ b/metrics_utils.cc
@@ -82,6 +82,7 @@
case ErrorCode::kNewRootfsVerificationError:
case ErrorCode::kNewKernelVerificationError:
+ case ErrorCode::kRollbackNotPossible:
return metrics::AttemptResult::kVerificationFailed;
case ErrorCode::kPostinstallRunnerError:
@@ -218,6 +219,7 @@
case ErrorCode::kOmahaUpdateIgnoredOverCellular:
case ErrorCode::kUpdatedButNotActive:
case ErrorCode::kNoUpdate:
+ case ErrorCode::kRollbackNotPossible:
break;
// Special flags. These can't happen (we mask them out above) but
diff --git a/omaha_request_action.cc b/omaha_request_action.cc
index 6809979..9021e3f 100644
--- a/omaha_request_action.cc
+++ b/omaha_request_action.cc
@@ -88,6 +88,9 @@
// updatecheck attributes (without the underscore prefix).
static const char* kEolAttr = "eol";
+static const char* kRollback = "rollback";
+static const char* kFirmwareVersion = "firmware_version";
+static const char* kKernelVersion = "kernel_version";
namespace {
@@ -996,6 +999,18 @@
// Parse the updatecheck attributes.
PersistEolStatus(parser_data->updatecheck_attrs);
+ // Rollback-related updatecheck attributes.
+ // Defaults to false if attribute is not present.
+ output_object->is_rollback =
+ ParseBool(parser_data->updatecheck_attrs[kRollback]);
+ // Defaults to 0 if attribute is not present. This is fine for these values,
+ // because it's the lowest possible value, and the rollback image won't be
+ // installed, if the values in the TPM are larger than these, and in case the
+ // values in the TPM are 0, all images will be able to boot up.
+ output_object->firmware_version = static_cast<uint32_t>(
+ ParseInt(parser_data->updatecheck_attrs[kFirmwareVersion]));
+ output_object->kernel_version = static_cast<uint32_t>(
+ ParseInt(parser_data->updatecheck_attrs[kKernelVersion]));
if (!ParseStatus(parser_data, output_object, completer))
return false;
diff --git a/omaha_response.h b/omaha_response.h
index b973eb5..e57f291 100644
--- a/omaha_response.h
+++ b/omaha_response.h
@@ -80,6 +80,15 @@
// PST, according to the Omaha Server's clock and timezone (PST8PDT,
// aka "Pacific Time".)
int install_date_days = -1;
+
+ // True if the returned image is a rollback for the device.
+ bool is_rollback = false;
+ // Kernel version of the returned rollback image. 0 if the image is not a
+ // rollback.
+ uint32_t kernel_version = 0;
+ // Firmware version of the returned rollback image. 0 if the image is not a
+ // rollback.
+ uint32_t firmware_version = 0;
};
static_assert(sizeof(off_t) == 8, "off_t not 64 bit");
diff --git a/omaha_response_handler_action.cc b/omaha_response_handler_action.cc
index f1a3310..3007f29 100644
--- a/omaha_response_handler_action.cc
+++ b/omaha_response_handler_action.cc
@@ -140,6 +140,26 @@
system_state_->prefs()->SetString(current_channel_key,
params->download_channel());
+ // Checking whether device is able to boot up the returned rollback image.
+ if (response.is_rollback) {
+ if (!params->rollback_allowed()) {
+ LOG(ERROR) << "Received rollback image but rollback is not allowed.";
+ completer.set_code(ErrorCode::kOmahaResponseInvalid);
+ return;
+ }
+ auto min_kernel_key_version = static_cast<uint32_t>(
+ system_state_->hardware()->GetMinKernelKeyVersion());
+ auto min_firmware_key_version = static_cast<uint32_t>(
+ system_state_->hardware()->GetMinFirmwareKeyVersion());
+ if (response.kernel_version < min_kernel_key_version ||
+ response.firmware_version < min_firmware_key_version) {
+ LOG(ERROR) << "Device won't be able to boot up the rollback image.";
+ completer.set_code(ErrorCode::kRollbackNotPossible);
+ return;
+ }
+ install_plan_.is_rollback = true;
+ }
+
if (params->ShouldPowerwash())
install_plan_.powerwash_required = true;
diff --git a/omaha_response_handler_action.h b/omaha_response_handler_action.h
index 2974841..e868b53 100644
--- a/omaha_response_handler_action.h
+++ b/omaha_response_handler_action.h
@@ -89,6 +89,8 @@
friend class OmahaResponseHandlerActionTest;
FRIEND_TEST(UpdateAttempterTest, CreatePendingErrorEventResumedTest);
+ FRIEND_TEST(UpdateAttempterTest, SetRollbackHappenedNotRollback);
+ FRIEND_TEST(UpdateAttempterTest, SetRollbackHappenedRollback);
FRIEND_TEST(UpdateAttempterTest, UpdateDeferredByPolicyTest);
DISALLOW_COPY_AND_ASSIGN(OmahaResponseHandlerAction);
diff --git a/omaha_response_handler_action_unittest.cc b/omaha_response_handler_action_unittest.cc
index aba71a2..55c642b 100644
--- a/omaha_response_handler_action_unittest.cc
+++ b/omaha_response_handler_action_unittest.cc
@@ -495,6 +495,103 @@
EXPECT_TRUE(install_plan.hash_checks_mandatory);
}
+TEST_F(OmahaResponseHandlerActionTest, RollbackTest) {
+ OmahaResponse in;
+ in.update_exists = true;
+ in.packages.push_back({.payload_urls = {"https://RollbackTest"},
+ .size = 1,
+ .hash = kPayloadHashHex});
+ in.is_rollback = true;
+ in.kernel_version = 0x00010002;
+ in.firmware_version = 0x00030004;
+
+ fake_system_state_.fake_hardware()->SetMinKernelKeyVersion(0x00010002);
+ fake_system_state_.fake_hardware()->SetMinFirmwareKeyVersion(0x00030004);
+
+ OmahaRequestParams params(&fake_system_state_);
+ params.set_rollback_allowed(true);
+
+ fake_system_state_.set_request_params(¶ms);
+ InstallPlan install_plan;
+ EXPECT_TRUE(DoTest(in, "", &install_plan));
+ EXPECT_TRUE(install_plan.is_rollback);
+}
+
+TEST_F(OmahaResponseHandlerActionTest, RollbackKernelVersionErrorTest) {
+ OmahaResponse in;
+ in.update_exists = true;
+ in.packages.push_back({.payload_urls = {"https://RollbackTest"},
+ .size = 1,
+ .hash = kPayloadHashHex});
+ in.is_rollback = true;
+ in.kernel_version = 0x00010001; // This is lower than the minimum.
+ in.firmware_version = 0x00030004;
+
+ fake_system_state_.fake_hardware()->SetMinKernelKeyVersion(0x00010002);
+ fake_system_state_.fake_hardware()->SetMinFirmwareKeyVersion(0x00030004);
+
+ OmahaRequestParams params(&fake_system_state_);
+ params.set_rollback_allowed(true);
+
+ fake_system_state_.set_request_params(¶ms);
+ InstallPlan install_plan;
+ EXPECT_FALSE(DoTest(in, "", &install_plan));
+}
+
+TEST_F(OmahaResponseHandlerActionTest, RollbackFirmwareVersionErrorTest) {
+ OmahaResponse in;
+ in.update_exists = true;
+ in.packages.push_back({.payload_urls = {"https://RollbackTest"},
+ .size = 1,
+ .hash = kPayloadHashHex});
+ in.is_rollback = true;
+ in.kernel_version = 0x00010002;
+ in.firmware_version = 0x00030003; // This is lower than the minimum.
+
+ fake_system_state_.fake_hardware()->SetMinKernelKeyVersion(0x00010002);
+ fake_system_state_.fake_hardware()->SetMinFirmwareKeyVersion(0x00030004);
+
+ OmahaRequestParams params(&fake_system_state_);
+ params.set_rollback_allowed(true);
+
+ fake_system_state_.set_request_params(¶ms);
+ InstallPlan install_plan;
+ EXPECT_FALSE(DoTest(in, "", &install_plan));
+}
+
+TEST_F(OmahaResponseHandlerActionTest, RollbackNotRollbackTest) {
+ OmahaResponse in;
+ in.update_exists = true;
+ in.packages.push_back({.payload_urls = {"https://RollbackTest"},
+ .size = 1,
+ .hash = kPayloadHashHex});
+ in.is_rollback = false;
+
+ OmahaRequestParams params(&fake_system_state_);
+ params.set_rollback_allowed(true);
+
+ fake_system_state_.set_request_params(¶ms);
+ InstallPlan install_plan;
+ EXPECT_TRUE(DoTest(in, "", &install_plan));
+ EXPECT_FALSE(install_plan.is_rollback);
+}
+
+TEST_F(OmahaResponseHandlerActionTest, RollbackNotAllowedTest) {
+ OmahaResponse in;
+ in.update_exists = true;
+ in.packages.push_back({.payload_urls = {"https://RollbackTest"},
+ .size = 1,
+ .hash = kPayloadHashHex});
+ in.is_rollback = true;
+
+ OmahaRequestParams params(&fake_system_state_);
+ params.set_rollback_allowed(false);
+
+ fake_system_state_.set_request_params(¶ms);
+ InstallPlan install_plan;
+ EXPECT_FALSE(DoTest(in, "", &install_plan));
+}
+
TEST_F(OmahaResponseHandlerActionTest, SystemVersionTest) {
OmahaResponse in;
in.update_exists = true;
diff --git a/payload_consumer/install_plan.h b/payload_consumer/install_plan.h
index 5cdfbc1..929cad3 100644
--- a/payload_consumer/install_plan.h
+++ b/payload_consumer/install_plan.h
@@ -127,6 +127,9 @@
// False otherwise.
bool run_post_install{true};
+ // True if this update is a rollback.
+ bool is_rollback{false};
+
// If not blank, a base-64 encoded representation of the PEM-encoded
// public key in the response.
std::string public_key_rsa;
diff --git a/payload_consumer/postinstall_runner_action.cc b/payload_consumer/postinstall_runner_action.cc
index cedecda..c672fef 100644
--- a/payload_consumer/postinstall_runner_action.cc
+++ b/payload_consumer/postinstall_runner_action.cc
@@ -57,7 +57,8 @@
CHECK(HasInputObject());
install_plan_ = GetInputObject();
- if (install_plan_.powerwash_required) {
+ // Currently we're always powerwashing when rolling back.
+ if (install_plan_.powerwash_required || install_plan_.is_rollback) {
if (hardware_->SchedulePowerwash()) {
powerwash_scheduled_ = true;
} else {
diff --git a/payload_consumer/postinstall_runner_action_unittest.cc b/payload_consumer/postinstall_runner_action_unittest.cc
index a319299..b7f0fdc 100644
--- a/payload_consumer/postinstall_runner_action_unittest.cc
+++ b/payload_consumer/postinstall_runner_action_unittest.cc
@@ -39,6 +39,7 @@
#include "update_engine/common/fake_hardware.h"
#include "update_engine/common/test_utils.h"
#include "update_engine/common/utils.h"
+#include "update_engine/mock_payload_state.h"
using brillo::MessageLoop;
using chromeos_update_engine::test_utils::ScopedLoopbackDeviceBinder;
@@ -95,9 +96,10 @@
// Setup an action processor and run the PostinstallRunnerAction with a single
// partition |device_path|, running the |postinstall_program| command from
// there.
- void RunPosinstallAction(const string& device_path,
- const string& postinstall_program,
- bool powerwash_required);
+ void RunPostinstallAction(const string& device_path,
+ const string& postinstall_program,
+ bool powerwash_required,
+ bool is_rollback);
public:
void ResumeRunningAction() {
@@ -163,10 +165,11 @@
ActionProcessor* processor_{nullptr};
};
-void PostinstallRunnerActionTest::RunPosinstallAction(
+void PostinstallRunnerActionTest::RunPostinstallAction(
const string& device_path,
const string& postinstall_program,
- bool powerwash_required) {
+ bool powerwash_required,
+ bool is_rollback) {
ActionProcessor processor;
processor_ = &processor;
ObjectFeederAction<InstallPlan> feeder_action;
@@ -179,6 +182,7 @@
install_plan.partitions = {part};
install_plan.download_url = "http://127.0.0.1:8080/update";
install_plan.powerwash_required = powerwash_required;
+ install_plan.is_rollback = is_rollback;
feeder_action.set_obj(install_plan);
PostinstallRunnerAction runner_action(&fake_boot_control_, &fake_hardware_);
postinstall_action_ = &runner_action;
@@ -241,7 +245,8 @@
// /postinst command which only exits 0.
TEST_F(PostinstallRunnerActionTest, RunAsRootSimpleTest) {
ScopedLoopbackDeviceBinder loop(postinstall_image_, false, nullptr);
- RunPosinstallAction(loop.dev(), kPostinstallDefaultScript, false);
+
+ RunPostinstallAction(loop.dev(), kPostinstallDefaultScript, false, false);
EXPECT_EQ(ErrorCode::kSuccess, processor_delegate_.code_);
EXPECT_TRUE(processor_delegate_.processing_done_called_);
@@ -251,14 +256,31 @@
TEST_F(PostinstallRunnerActionTest, RunAsRootRunSymlinkFileTest) {
ScopedLoopbackDeviceBinder loop(postinstall_image_, false, nullptr);
- RunPosinstallAction(loop.dev(), "bin/postinst_link", false);
+ RunPostinstallAction(loop.dev(), "bin/postinst_link", false, false);
EXPECT_EQ(ErrorCode::kSuccess, processor_delegate_.code_);
}
TEST_F(PostinstallRunnerActionTest, RunAsRootPowerwashRequiredTest) {
ScopedLoopbackDeviceBinder loop(postinstall_image_, false, nullptr);
// Run a simple postinstall program but requiring a powerwash.
- RunPosinstallAction(loop.dev(), "bin/postinst_example", true);
+ RunPostinstallAction(loop.dev(),
+ "bin/postinst_example",
+ /*powerwash_required=*/true,
+ false);
+ EXPECT_EQ(ErrorCode::kSuccess, processor_delegate_.code_);
+
+ // Check that powerwash was scheduled.
+ EXPECT_TRUE(fake_hardware_.IsPowerwashScheduled());
+}
+
+TEST_F(PostinstallRunnerActionTest, RunAsRootRollbackTest) {
+ ScopedLoopbackDeviceBinder loop(postinstall_image_, false, nullptr);
+
+ // Run a simple postinstall program, rollback happened.
+ RunPostinstallAction(loop.dev(),
+ "bin/postinst_example",
+ false,
+ /*is_rollback=*/true);
EXPECT_EQ(ErrorCode::kSuccess, processor_delegate_.code_);
// Check that powerwash was scheduled.
@@ -268,7 +290,7 @@
// Runs postinstall from a partition file that doesn't mount, so it should
// fail.
TEST_F(PostinstallRunnerActionTest, RunAsRootCantMountTest) {
- RunPosinstallAction("/dev/null", kPostinstallDefaultScript, false);
+ RunPostinstallAction("/dev/null", kPostinstallDefaultScript, false, false);
EXPECT_EQ(ErrorCode::kPostinstallRunnerError, processor_delegate_.code_);
// In case of failure, Postinstall should not signal a powerwash even if it
@@ -280,7 +302,7 @@
// fail.
TEST_F(PostinstallRunnerActionTest, RunAsRootErrScriptTest) {
ScopedLoopbackDeviceBinder loop(postinstall_image_, false, nullptr);
- RunPosinstallAction(loop.dev(), "bin/postinst_fail1", false);
+ RunPostinstallAction(loop.dev(), "bin/postinst_fail1", false, false);
EXPECT_EQ(ErrorCode::kPostinstallRunnerError, processor_delegate_.code_);
}
@@ -288,7 +310,7 @@
// UMA with a different error code. Test those cases are properly detected.
TEST_F(PostinstallRunnerActionTest, RunAsRootFirmwareBErrScriptTest) {
ScopedLoopbackDeviceBinder loop(postinstall_image_, false, nullptr);
- RunPosinstallAction(loop.dev(), "bin/postinst_fail3", false);
+ RunPostinstallAction(loop.dev(), "bin/postinst_fail3", false, false);
EXPECT_EQ(ErrorCode::kPostinstallBootedFromFirmwareB,
processor_delegate_.code_);
}
@@ -296,7 +318,7 @@
// Check that you can't specify an absolute path.
TEST_F(PostinstallRunnerActionTest, RunAsRootAbsolutePathNotAllowedTest) {
ScopedLoopbackDeviceBinder loop(postinstall_image_, false, nullptr);
- RunPosinstallAction(loop.dev(), "/etc/../bin/sh", false);
+ RunPostinstallAction(loop.dev(), "/etc/../bin/sh", false, false);
EXPECT_EQ(ErrorCode::kPostinstallRunnerError, processor_delegate_.code_);
}
@@ -305,7 +327,7 @@
// SElinux labels are only set on Android.
TEST_F(PostinstallRunnerActionTest, RunAsRootCheckFileContextsTest) {
ScopedLoopbackDeviceBinder loop(postinstall_image_, false, nullptr);
- RunPosinstallAction(loop.dev(), "bin/self_check_context", false);
+ RunPosinstallAction(loop.dev(), "bin/self_check_context", false, false);
EXPECT_EQ(ErrorCode::kSuccess, processor_delegate_.code_);
}
#endif // __ANDROID__
@@ -318,7 +340,7 @@
loop_.PostTask(FROM_HERE,
base::Bind(&PostinstallRunnerActionTest::SuspendRunningAction,
base::Unretained(this)));
- RunPosinstallAction(loop.dev(), "bin/postinst_suspend", false);
+ RunPostinstallAction(loop.dev(), "bin/postinst_suspend", false, false);
// postinst_suspend returns 0 only if it was suspended at some point.
EXPECT_EQ(ErrorCode::kSuccess, processor_delegate_.code_);
EXPECT_TRUE(processor_delegate_.processing_done_called_);
@@ -330,7 +352,7 @@
// Wait for the action to start and then cancel it.
CancelWhenStarted();
- RunPosinstallAction(loop.dev(), "bin/postinst_suspend", false);
+ RunPostinstallAction(loop.dev(), "bin/postinst_suspend", false, false);
// When canceling the action, the action never finished and therefore we had
// a ProcessingStopped call instead.
EXPECT_FALSE(processor_delegate_.code_set_);
@@ -353,7 +375,7 @@
ScopedLoopbackDeviceBinder loop(postinstall_image_, false, nullptr);
setup_action_delegate_ = &mock_delegate_;
- RunPosinstallAction(loop.dev(), "bin/postinst_progress", false);
+ RunPostinstallAction(loop.dev(), "bin/postinst_progress", false, false);
EXPECT_EQ(ErrorCode::kSuccess, processor_delegate_.code_);
}
diff --git a/payload_state.cc b/payload_state.cc
index 7c969b3..03f74af 100644
--- a/payload_state.cc
+++ b/payload_state.cc
@@ -359,6 +359,7 @@
case ErrorCode::kOmahaUpdateIgnoredOverCellular:
case ErrorCode::kUpdatedButNotActive:
case ErrorCode::kNoUpdate:
+ case ErrorCode::kRollbackNotPossible:
LOG(INFO) << "Not incrementing URL index or failure count for this error";
break;
diff --git a/update_attempter.cc b/update_attempter.cc
index db4be6b..4a71e4d 100644
--- a/update_attempter.cc
+++ b/update_attempter.cc
@@ -1003,6 +1003,12 @@
payload.metadata_signature + ":";
}
+ // If we just downloaded a rollback image, we should preserve this fact
+ // over the following powerwash.
+ if (install_plan.is_rollback) {
+ system_state_->payload_state()->SetRollbackHappened(true);
+ }
+
// Expect to reboot into the new version to send the proper metric during
// next boot.
system_state_->payload_state()->ExpectRebootInNewVersion(
diff --git a/update_attempter.h b/update_attempter.h
index 4b51478..a3e2b30 100644
--- a/update_attempter.h
+++ b/update_attempter.h
@@ -272,6 +272,8 @@
FRIEND_TEST(UpdateAttempterTest, RollbackAllowedSetAndReset);
FRIEND_TEST(UpdateAttempterTest, ScheduleErrorEventActionNoEventTest);
FRIEND_TEST(UpdateAttempterTest, ScheduleErrorEventActionTest);
+ FRIEND_TEST(UpdateAttempterTest, SetRollbackHappenedNotRollback);
+ FRIEND_TEST(UpdateAttempterTest, SetRollbackHappenedRollback);
FRIEND_TEST(UpdateAttempterTest, TargetVersionPrefixSetAndReset);
FRIEND_TEST(UpdateAttempterTest, UpdateAttemptFlagsCachedAtUpdateStart);
FRIEND_TEST(UpdateAttempterTest, UpdateDeferredByPolicyTest);
diff --git a/update_attempter_unittest.cc b/update_attempter_unittest.cc
index 54a3f2b..3936404 100644
--- a/update_attempter_unittest.cc
+++ b/update_attempter_unittest.cc
@@ -1243,4 +1243,28 @@
loop_.Run();
}
+TEST_F(UpdateAttempterTest, SetRollbackHappenedRollback) {
+ OmahaResponseHandlerAction* response_action =
+ new OmahaResponseHandlerAction(&fake_system_state_);
+ response_action->install_plan_.is_rollback = true;
+ attempter_.response_handler_action_.reset(response_action);
+
+ EXPECT_CALL(*fake_system_state_.mock_payload_state(),
+ SetRollbackHappened(true))
+ .Times(1);
+ attempter_.ProcessingDone(nullptr, ErrorCode::kSuccess);
+}
+
+TEST_F(UpdateAttempterTest, SetRollbackHappenedNotRollback) {
+ OmahaResponseHandlerAction* response_action =
+ new OmahaResponseHandlerAction(&fake_system_state_);
+ response_action->install_plan_.is_rollback = false;
+ attempter_.response_handler_action_.reset(response_action);
+
+ EXPECT_CALL(*fake_system_state_.mock_payload_state(),
+ SetRollbackHappened(true))
+ .Times(0);
+ attempter_.ProcessingDone(nullptr, ErrorCode::kSuccess);
+}
+
} // namespace chromeos_update_engine
diff --git a/update_manager/chromeos_policy.cc b/update_manager/chromeos_policy.cc
index 655ec82..d56a22e 100644
--- a/update_manager/chromeos_policy.cc
+++ b/update_manager/chromeos_policy.cc
@@ -144,6 +144,7 @@
case ErrorCode::kOmahaUpdateIgnoredOverCellular:
case ErrorCode::kUpdatedButNotActive:
case ErrorCode::kNoUpdate:
+ case ErrorCode::kRollbackNotPossible:
LOG(INFO) << "Not changing URL index or failure count due to error "
<< chromeos_update_engine::utils::ErrorCodeToString(err_code)
<< " (" << static_cast<int>(err_code) << ")";