AU: Resume interrupted update attempts.
BUG=7390,7520
TEST=unit tests
Change-Id: I9baf72aa444dd855409f865f03fb665e91f8d03d
Review URL: http://codereview.chromium.org/3620013
diff --git a/delta_performer.cc b/delta_performer.cc
index 10031eb..0eccb09 100644
--- a/delta_performer.cc
+++ b/delta_performer.cc
@@ -198,7 +198,10 @@
manifest_metadata_size_))
<< "Unable to save the manifest metadata size.";
manifest_valid_ = true;
- block_size_ = manifest_.block_size();
+ if (!PrimeUpdateState()) {
+ LOG(ERROR) << "Unable to prime the update state.";
+ return -EINVAL;
+ }
}
ssize_t total_operations = manifest_.install_operations_size() +
manifest_.kernel_install_operations_size();
@@ -225,7 +228,7 @@
// update.
if (!IsIdempotentOperation(op)) {
Terminator::set_exit_blocked(true);
- ResetUpdateProgress(prefs_);
+ ResetUpdateProgress(prefs_, true);
}
if (op.type() == DeltaArchiveManifest_InstallOperation_Type_REPLACE ||
op.type() == DeltaArchiveManifest_InstallOperation_Type_REPLACE_BZ) {
@@ -281,8 +284,8 @@
// Since we delete data off the beginning of the buffer as we use it,
// the data we need should be exactly at the beginning of the buffer.
- CHECK_EQ(buffer_offset_, operation.data_offset());
- CHECK_GE(buffer_.size(), operation.data_length());
+ TEST_AND_RETURN_FALSE(buffer_offset_ == operation.data_offset());
+ TEST_AND_RETURN_FALSE(buffer_.size() >= operation.data_length());
// Extract the signature message if it's in this operation.
ExtractSignatureMessage(operation);
@@ -405,8 +408,8 @@
bool is_kernel_partition) {
// Since we delete data off the beginning of the buffer as we use it,
// the data we need should be exactly at the beginning of the buffer.
- CHECK_EQ(buffer_offset_, operation.data_offset());
- CHECK_GE(buffer_.size(), operation.data_length());
+ TEST_AND_RETURN_FALSE(buffer_offset_ == operation.data_offset());
+ TEST_AND_RETURN_FALSE(buffer_.size() >= operation.data_length());
string input_positions;
TEST_AND_RETURN_FALSE(ExtentsToBsdiffPositionsString(operation.src_extents(),
@@ -571,9 +574,16 @@
return true;
}
-bool DeltaPerformer::ResetUpdateProgress(PrefsInterface* prefs) {
+bool DeltaPerformer::ResetUpdateProgress(PrefsInterface* prefs, bool quick) {
TEST_AND_RETURN_FALSE(prefs->SetInt64(kPrefsUpdateStateNextOperation,
kUpdateStateOperationInvalid));
+ if (!quick) {
+ prefs->SetString(kPrefsUpdateCheckResponseHash, "");
+ prefs->SetInt64(kPrefsUpdateStateNextDataOffset, -1);
+ prefs->SetString(kPrefsUpdateStateSHA256Context, "");
+ prefs->SetString(kPrefsUpdateStateSignedSHA256Context, "");
+ prefs->SetInt64(kPrefsManifestMetadataSize, -1);
+ }
return true;
}
@@ -581,7 +591,7 @@
Terminator::set_exit_blocked(true);
if (last_updated_buffer_offset_ != buffer_offset_) {
// Resets the progress in case we die in the middle of the state update.
- ResetUpdateProgress(prefs_);
+ ResetUpdateProgress(prefs_, true);
TEST_AND_RETURN_FALSE(
prefs_->SetString(kPrefsUpdateStateSHA256Context,
hash_calculator_.GetContext()));
@@ -594,4 +604,43 @@
return true;
}
+bool DeltaPerformer::PrimeUpdateState() {
+ CHECK(manifest_valid_);
+ block_size_ = manifest_.block_size();
+
+ int64_t next_operation = kUpdateStateOperationInvalid;
+ if (!prefs_->GetInt64(kPrefsUpdateStateNextOperation, &next_operation) ||
+ next_operation == kUpdateStateOperationInvalid ||
+ next_operation <= 0) {
+ // Initiating a new update, no more state needs to be initialized.
+ return true;
+ }
+ next_operation_num_ = next_operation;
+
+ // Resuming an update -- load the rest of the update state.
+ int64_t next_data_offset = -1;
+ TEST_AND_RETURN_FALSE(prefs_->GetInt64(kPrefsUpdateStateNextDataOffset,
+ &next_data_offset) &&
+ next_data_offset >= 0);
+ buffer_offset_ = next_data_offset;
+
+ // The signed hash context may be empty if the interrupted update didn't reach
+ // the signature blob.
+ prefs_->GetString(kPrefsUpdateStateSignedSHA256Context,
+ &signed_hash_context_);
+
+ string hash_context;
+ TEST_AND_RETURN_FALSE(prefs_->GetString(kPrefsUpdateStateSHA256Context,
+ &hash_context) &&
+ hash_calculator_.SetContext(hash_context));
+
+ int64_t manifest_metadata_size = 0;
+ TEST_AND_RETURN_FALSE(prefs_->GetInt64(kPrefsManifestMetadataSize,
+ &manifest_metadata_size) &&
+ manifest_metadata_size > 0);
+ manifest_metadata_size_ = manifest_metadata_size;
+
+ return true;
+}
+
} // namespace chromeos_update_engine
diff --git a/delta_performer.h b/delta_performer.h
index 5425ea5..051d605 100644
--- a/delta_performer.h
+++ b/delta_performer.h
@@ -83,8 +83,10 @@
std::string update_check_response_hash);
// Resets the persistent update progress state to indicate that an update
- // can't be resumed. Returns true on success, false otherwise.
- static bool ResetUpdateProgress(PrefsInterface* prefs);
+ // can't be resumed. Performs a quick update-in-progress reset if |quick| is
+ // true, otherwise resets all progress-related update state. Returns true on
+ // success, false otherwise.
+ static bool ResetUpdateProgress(PrefsInterface* prefs, bool quick);
private:
// Returns true if enough of the delta file has been passed via Write()
@@ -120,6 +122,11 @@
// update attempt to be resumed after reboot.
bool CheckpointUpdateProgress();
+ // Primes the required update state. Returns true if the update state was
+ // successfully initialized to a saved resume state or if the update is a new
+ // update. Returns false otherwise.
+ bool PrimeUpdateState();
+
// Update Engine preference store.
PrefsInterface* prefs_;
diff --git a/delta_performer_unittest.cc b/delta_performer_unittest.cc
index 5565c88..07fd8cc 100755
--- a/delta_performer_unittest.cc
+++ b/delta_performer_unittest.cc
@@ -259,6 +259,8 @@
manifest_metadata_size)).WillOnce(Return(true));
EXPECT_CALL(prefs, SetInt64(kPrefsUpdateStateNextOperation, _))
.WillRepeatedly(Return(true));
+ EXPECT_CALL(prefs, GetInt64(kPrefsUpdateStateNextOperation, _))
+ .WillOnce(Return(false));
EXPECT_CALL(prefs, SetInt64(kPrefsUpdateStateNextDataOffset, _))
.WillRepeatedly(Return(true));
EXPECT_CALL(prefs, SetString(kPrefsUpdateStateSHA256Context, _))
diff --git a/download_action.h b/download_action.h
index 62dd29a..08035f0 100644
--- a/download_action.h
+++ b/download_action.h
@@ -93,6 +93,8 @@
delegate_ = delegate;
}
+ HttpFetcher* http_fetcher() { return http_fetcher_.get(); }
+
private:
// The InstallPlan passed in
InstallPlan install_plan_;
diff --git a/filesystem_copier_action.cc b/filesystem_copier_action.cc
index 388e0e7..5628ec3 100755
--- a/filesystem_copier_action.cc
+++ b/filesystem_copier_action.cc
@@ -1,21 +1,25 @@
-// Copyright (c) 2010 The Chromium Authors. All rights reserved.
+// Copyright (c) 2010 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "update_engine/filesystem_copier_action.h"
+
#include <sys/stat.h>
#include <sys/types.h>
#include <errno.h>
#include <fcntl.h>
-#include <stdlib.h>
+
#include <algorithm>
+#include <cstdlib>
#include <map>
#include <string>
#include <vector>
+
#include <gio/gio.h>
#include <gio/gunixinputstream.h>
#include <gio/gunixoutputstream.h>
#include <glib.h>
+
#include "update_engine/filesystem_iterator.h"
#include "update_engine/subprocess.h"
#include "update_engine/utils.h"
@@ -41,7 +45,7 @@
}
install_plan_ = GetInputObject();
- if (install_plan_.is_full_update) {
+ if (install_plan_.is_full_update || install_plan_.is_resume) {
// No copy needed. Done!
if (HasOutputPipe())
SetOutputObject(install_plan_);
diff --git a/filesystem_copier_action_unittest.cc b/filesystem_copier_action_unittest.cc
index f82097e..d9305ff 100644
--- a/filesystem_copier_action_unittest.cc
+++ b/filesystem_copier_action_unittest.cc
@@ -249,6 +249,32 @@
EXPECT_EQ(kUrl, collector_action.object().download_url);
}
+TEST_F(FilesystemCopierActionTest, ResumeTest) {
+ ActionProcessor processor;
+ FilesystemCopierActionTest2Delegate delegate;
+
+ processor.set_delegate(&delegate);
+
+ ObjectFeederAction<InstallPlan> feeder_action;
+ const char* kUrl = "http://some/url";
+ InstallPlan install_plan(false, true, kUrl, 0, "", "", "");
+ feeder_action.set_obj(install_plan);
+ FilesystemCopierAction copier_action(false);
+ ObjectCollectorAction<InstallPlan> collector_action;
+
+ BondActions(&feeder_action, &copier_action);
+ BondActions(&copier_action, &collector_action);
+
+ processor.EnqueueAction(&feeder_action);
+ processor.EnqueueAction(&copier_action);
+ processor.EnqueueAction(&collector_action);
+ processor.StartProcessing();
+ EXPECT_FALSE(processor.IsRunning());
+ EXPECT_TRUE(delegate.ran_);
+ EXPECT_EQ(kActionCodeSuccess, delegate.code_);
+ EXPECT_EQ(kUrl, collector_action.object().download_url);
+}
+
TEST_F(FilesystemCopierActionTest, NonExistentDriveTest) {
ActionProcessor processor;
FilesystemCopierActionTest2Delegate delegate;
diff --git a/generate_delta_main.cc b/generate_delta_main.cc
index 8c5d6a9..a113e15 100644
--- a/generate_delta_main.cc
+++ b/generate_delta_main.cc
@@ -91,6 +91,7 @@
CHECK_EQ(performer.Write(&buf[0], bytes_read), bytes_read);
}
CHECK_EQ(performer.Close(), 0);
+ DeltaPerformer::ResetUpdateProgress(&prefs, false);
LOG(INFO) << "done applying delta.";
return 0;
}
diff --git a/multi_http_fetcher.h b/multi_http_fetcher.h
index cee7de0..1972347 100644
--- a/multi_http_fetcher.h
+++ b/multi_http_fetcher.h
@@ -40,7 +40,7 @@
(*it)->set_delegate(this);
}
}
-
+
void SetOffset(off_t offset) {} // for now, doesn't support this
// Begins the transfer to the specified URL.
@@ -100,17 +100,18 @@
if (delegate_)
delegate_->TransferComplete(this, successful);
}
-
+
void StartTransfer() {
if (current_index_ >= ranges_.size()) {
return;
}
- LOG(INFO) << "Starting a transfer";
+ LOG(INFO) << "Starting a transfer @" << ranges_[current_index_].first << "("
+ << ranges_[current_index_].second << ")";
bytes_received_this_fetcher_ = 0;
fetchers_[current_index_]->SetOffset(ranges_[current_index_].first);
fetchers_[current_index_]->BeginTransfer(url_);
}
-
+
void ReceivedBytes(HttpFetcher* fetcher,
const char* bytes,
int length) {
@@ -148,7 +149,7 @@
SendTransferComplete(fetcher, true);
return;
}
-
+
if (ranges_[current_index_].second < 0) {
// We're done with the current operation
current_index_++;
@@ -160,7 +161,7 @@
}
return;
}
-
+
if (bytes_received_this_fetcher_ < ranges_[current_index_].second) {
LOG(WARNING) << "Received insufficient bytes from fetcher. "
<< "Ending early";
@@ -170,13 +171,13 @@
LOG(INFO) << "Got spurious TransferComplete. Ingoring.";
}
}
-
+
// If true, do not send any more data or TransferComplete to the delegate.
bool sent_transfer_complete_;
-
+
RangesVect ranges_;
std::vector<std::tr1::shared_ptr<BaseHttpFetcher> > fetchers_;
-
+
RangesVect::size_type current_index_; // index into ranges_, fetchers_
off_t bytes_received_this_fetcher_;
diff --git a/omaha_response_handler_action.cc b/omaha_response_handler_action.cc
index d3142a7..5159733 100644
--- a/omaha_response_handler_action.cc
+++ b/omaha_response_handler_action.cc
@@ -32,7 +32,7 @@
install_plan_.is_resume =
DeltaPerformer::CanResumeUpdate(prefs_, response.hash);
if (!install_plan_.is_resume) {
- LOG_IF(WARNING, !DeltaPerformer::ResetUpdateProgress(prefs_))
+ LOG_IF(WARNING, !DeltaPerformer::ResetUpdateProgress(prefs_, false))
<< "Unable to reset the update progress.";
LOG_IF(WARNING, !prefs_->SetString(kPrefsUpdateCheckResponseHash,
response.hash))
diff --git a/update_attempter.cc b/update_attempter.cc
index 2db91a6..f408d82 100644
--- a/update_attempter.cc
+++ b/update_attempter.cc
@@ -10,8 +10,8 @@
#endif // _POSIX_C_SOURCE
#include <time.h>
-#include <tr1/memory>
#include <string>
+#include <tr1/memory>
#include <vector>
#include <glib.h>
@@ -21,6 +21,7 @@
#include "update_engine/download_action.h"
#include "update_engine/filesystem_copier_action.h"
#include "update_engine/libcurl_http_fetcher.h"
+#include "update_engine/multi_http_fetcher.h"
#include "update_engine/omaha_request_action.h"
#include "update_engine/omaha_request_params.h"
#include "update_engine/omaha_response_handler_action.h"
@@ -31,6 +32,7 @@
using base::TimeDelta;
using base::TimeTicks;
+using std::make_pair;
using std::tr1::shared_ptr;
using std::string;
using std::vector;
@@ -153,7 +155,7 @@
OmahaEvent::kTypeUpdateDownloadStarted),
new LibcurlHttpFetcher));
shared_ptr<DownloadAction> download_action(
- new DownloadAction(prefs_, new LibcurlHttpFetcher));
+ new DownloadAction(prefs_, new MultiHttpFetcher<LibcurlHttpFetcher>));
shared_ptr<OmahaRequestAction> download_finished_action(
new OmahaRequestAction(prefs_,
omaha_request_params_,
@@ -174,6 +176,7 @@
download_action->set_delegate(this);
response_handler_action_ = response_handler_action;
+ download_action_ = download_action;
actions_.push_back(shared_ptr<AbstractAction>(update_check_action));
actions_.push_back(shared_ptr<AbstractAction>(response_handler_action));
@@ -254,9 +257,10 @@
}
if (code == kActionCodeSuccess) {
- SetStatusAndNotify(UPDATE_STATUS_UPDATED_NEED_REBOOT);
utils::WriteFile(kUpdateCompletedMarker, "", 0);
prefs_->SetInt64(kPrefsDeltaUpdateFailures, 0);
+ DeltaPerformer::ResetUpdateProgress(prefs_, false);
+ SetStatusAndNotify(UPDATE_STATUS_UPDATED_NEED_REBOOT);
// Report the time it took to update the system.
int64_t update_time = time(NULL) - last_checked_time_;
@@ -327,20 +331,18 @@
}
// Find out which action completed.
if (type == OmahaResponseHandlerAction::StaticType()) {
- // Note that the status will be updated to DOWNLOADING when some
- // bytes get actually downloaded from the server and the
- // BytesReceived callback is invoked. This avoids notifying the
- // user that a download has started in cases when the server and
- // the client are unable to initiate the download.
- OmahaResponseHandlerAction* omaha_response_handler_action =
- dynamic_cast<OmahaResponseHandlerAction*>(action);
- CHECK(omaha_response_handler_action);
- const InstallPlan& plan = omaha_response_handler_action->install_plan();
+ // Note that the status will be updated to DOWNLOADING when some bytes get
+ // actually downloaded from the server and the BytesReceived callback is
+ // invoked. This avoids notifying the user that a download has started in
+ // cases when the server and the client are unable to initiate the download.
+ CHECK(action == response_handler_action_.get());
+ const InstallPlan& plan = response_handler_action_->install_plan();
last_checked_time_ = time(NULL);
// TODO(adlr): put version in InstallPlan
new_version_ = "0.0.0.0";
new_size_ = plan.size;
is_full_update_ = plan.is_full_update;
+ SetupDownload();
SetupPriorityManagement();
} else if (type == DownloadAction::StaticType()) {
SetStatusAndNotify(UPDATE_STATUS_FINALIZING);
@@ -517,6 +519,11 @@
void UpdateAttempter::MarkDeltaUpdateFailure() {
CHECK(!is_full_update_);
+ // If a delta update fails after the downloading phase, don't try to resume it
+ // the next time.
+ if (status_ > UPDATE_STATUS_DOWNLOADING) {
+ DeltaPerformer::ResetUpdateProgress(prefs_, false);
+ }
int64_t delta_failures;
if (!prefs_->GetInt64(kPrefsDeltaUpdateFailures, &delta_failures) ||
delta_failures < 0) {
@@ -525,4 +532,22 @@
prefs_->SetInt64(kPrefsDeltaUpdateFailures, ++delta_failures);
}
+void UpdateAttempter::SetupDownload() {
+ MultiHttpFetcher<LibcurlHttpFetcher>* fetcher =
+ dynamic_cast<MultiHttpFetcher<LibcurlHttpFetcher>*>(
+ download_action_->http_fetcher());
+ MultiHttpFetcher<LibcurlHttpFetcher>::RangesVect ranges;
+ if (response_handler_action_->install_plan().is_resume) {
+ int64_t manifest_metadata_size = 0;
+ prefs_->GetInt64(kPrefsManifestMetadataSize, &manifest_metadata_size);
+ int64_t next_data_offset = 0;
+ prefs_->GetInt64(kPrefsUpdateStateNextDataOffset, &next_data_offset);
+ ranges.push_back(make_pair(0, manifest_metadata_size));
+ ranges.push_back(make_pair(manifest_metadata_size + next_data_offset, -1));
+ } else {
+ ranges.push_back(make_pair(0, -1));
+ }
+ fetcher->set_ranges(ranges);
+}
+
} // namespace chromeos_update_engine
diff --git a/update_attempter.h b/update_attempter.h
index 05f3dcb..db05c7b 100644
--- a/update_attempter.h
+++ b/update_attempter.h
@@ -116,33 +116,36 @@
// over dbus.
void SetStatusAndNotify(UpdateStatus status);
- // Creates an error event object in |error_event_| to be included in
- // an OmahaRequestAction once the current action processor is done.
+ // Sets up the download parameters after receiving the update check response.
+ void SetupDownload();
+
+ // Creates an error event object in |error_event_| to be included in an
+ // OmahaRequestAction once the current action processor is done.
void CreatePendingErrorEvent(AbstractAction* action, ActionExitCode code);
- // If there's a pending error event allocated in |error_event_|,
- // schedules an OmahaRequestAction with that event in the current
- // processor, clears the pending event, updates the status and
- // returns true. Returns false otherwise.
+ // If there's a pending error event allocated in |error_event_|, schedules an
+ // OmahaRequestAction with that event in the current processor, clears the
+ // pending event, updates the status and returns true. Returns false
+ // otherwise.
bool ScheduleErrorEventAction();
- // Sets the process priority to |priority| and updates |priority_|
- // if the new |priority| is different than the current |priority_|,
- // otherwise simply returns.
+ // Sets the process priority to |priority| and updates |priority_| if the new
+ // |priority| is different than the current |priority_|, otherwise simply
+ // returns.
void SetPriority(utils::ProcessPriority priority);
- // Set the process priority to low and sets up timeout events to
- // increase the priority gradually to high.
+ // Set the process priority to low and sets up timeout events to increase the
+ // priority gradually to high.
void SetupPriorityManagement();
- // Resets the process priority to normal and destroys any scheduled
- // timeout sources.
+ // Resets the process priority to normal and destroys any scheduled timeout
+ // sources.
void CleanupPriorityManagement();
- // The process priority timeout source callback increases the
- // current priority by one step (low goes to normal, normal goes to
- // high). Returns true if the callback must be invoked again after a
- // timeout, or false if GLib can destroy this timeout source.
+ // The process priority timeout source callback increases the current priority
+ // by one step (low goes to normal, normal goes to high). Returns true if the
+ // callback must be invoked again after a timeout, or false if GLib can
+ // destroy this timeout source.
static gboolean StaticManagePriorityCallback(gpointer data);
bool ManagePriorityCallback();
@@ -154,9 +157,9 @@
// update can be tried when needed.
void MarkDeltaUpdateFailure();
- // Last status notification timestamp used for throttling. Use
- // monotonic TimeTicks to ensure that notifications are sent even if
- // the system clock is set back in the middle of an update.
+ // Last status notification timestamp used for throttling. Use monotonic
+ // TimeTicks to ensure that notifications are sent even if the system clock is
+ // set back in the middle of an update.
base::TimeTicks last_notify_time_;
std::vector<std::tr1::shared_ptr<AbstractAction> > actions_;
@@ -166,9 +169,12 @@
// dbus service.
UpdateEngineService* dbus_service_;
- // pointer to the OmahaResponseHandlerAction in the actions_ vector;
+ // Pointer to the OmahaResponseHandlerAction in the actions_ vector.
std::tr1::shared_ptr<OmahaResponseHandlerAction> response_handler_action_;
+ // Pointer to the DownloadAction in the actions_ vector.
+ std::tr1::shared_ptr<DownloadAction> download_action_;
+
// Pointer to the preferences store interface.
PrefsInterface* prefs_;