GPIO test signal safe fallback + refactoring.
This addresses the problem of spurious GPIO signals indicating a test
scenario whereas in fact this isn't one, which may lead to hosts unable
to get an update (ZGB, Lumpy). This is also a partial fix to a problem
with Stumpy, where such spurious signals are inherent to the board
implementation.
* Safe fallback: a GPIO-signaled test scenario will be ignored other
than on the first time, in both places it is being checked
(UpdateAttempter::Update() and UpdateCheckScheduler::StaticCheck()).
This will ensure that we do not (a) override EULA/OOBE-complete flag
more than once; and (b) we do not attempt to update against a local
test server more than once. This generally covers against spurious
GPIO, as long as a user cannot trigger an update check on
a non-OOBE-complete system (appears to be a safe assumption).
* The retry timeout after failing an update with the test server is
shortened to 1 minute (compared to the default 45 minute). This
substantially increases the chances for a system exhibiting spurious
GPIO signals to get updates.
* Moved the GPIO functionality into a separate module/class. This makes
more sense now that it is being used by more than one class
(UpdateAttempter and UpdateCheckScheduler). The implementation of
GpioHandler has no instance data members and so behaves like
a singleton, but otherwise looks and feels like a normal class.
* Also changing the private test server URL to use an unregistered TCP
port (further reduces the chances of anything responding on the LAN).
* Some minor fixes.
BUG=chromium-os:27077, chromium-os:27109, chromium-os:25397,
chromium-os:27157
TEST=Unittests passed; GPIO reading + fallback work on x86-alex.
Change-Id: Ide1a60a690f1263efd47872360470347e56eeb45
Reviewed-on: https://gerrit.chromium.org/gerrit/17344
Commit-Ready: Gilad Arnold <garnold@chromium.org>
Reviewed-by: Gilad Arnold <garnold@chromium.org>
Tested-by: Gilad Arnold <garnold@chromium.org>
diff --git a/SConstruct b/SConstruct
index e7e9783..ba33d52 100644
--- a/SConstruct
+++ b/SConstruct
@@ -256,6 +256,7 @@
file_writer.cc
flimflam_proxy.cc
full_update_generator.cc
+ gpio_handler.cc
graph_utils.cc
http_common.cc
http_fetcher.cc
diff --git a/gpio_handler.cc b/gpio_handler.cc
new file mode 100644
index 0000000..d1825bd
--- /dev/null
+++ b/gpio_handler.cc
@@ -0,0 +1,337 @@
+// Copyright (c) 2012 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 "gpio_handler.h"
+
+#include <fcntl.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include <base/eintr_wrapper.h>
+#include <base/string_util.h>
+#include <glib.h>
+
+#include "update_engine/utils.h"
+
+using std::string;
+
+namespace chromeos_update_engine {
+
+const char* GpioHandler::dutflaga_dev_name_ = NULL;
+const char* GpioHandler::dutflagb_dev_name_ = NULL;
+
+namespace {
+// Names of udev properties that are linked to the GPIO chip device and identify
+// the two dutflag GPIOs on different boards.
+const char kIdGpioDutflaga[] = "ID_GPIO_DUTFLAGA";
+const char kIdGpioDutflagb[] = "ID_GPIO_DUTFLAGB";
+
+// Scoped closer for udev and udev_enumerate objects.
+// TODO(garnold) chromium-os:26934: it would be nice to generalize the different
+// ScopedFooCloser implementations in update engine using a single template.
+class ScopedUdevCloser {
+ public:
+ explicit ScopedUdevCloser(udev** udev_p) : udev_p_(udev_p) {}
+ ~ScopedUdevCloser() {
+ if (udev_p_ && *udev_p_) {
+ udev_unref(*udev_p_);
+ *udev_p_ = NULL;
+ }
+ }
+ private:
+ struct udev **udev_p_;
+
+ DISALLOW_COPY_AND_ASSIGN(ScopedUdevCloser);
+};
+
+class ScopedUdevEnumerateCloser {
+ public:
+ explicit ScopedUdevEnumerateCloser(udev_enumerate **udev_enum_p) :
+ udev_enum_p_(udev_enum_p) {}
+ ~ScopedUdevEnumerateCloser() {
+ if (udev_enum_p_ && *udev_enum_p_) {
+ udev_enumerate_unref(*udev_enum_p_);
+ *udev_enum_p_ = NULL;
+ }
+ }
+ private:
+ struct udev_enumerate** udev_enum_p_;
+
+ DISALLOW_COPY_AND_ASSIGN(ScopedUdevEnumerateCloser);
+};
+} // namespace {}
+
+bool GpioHandler::IsGpioSignalingTest() {
+ // Peek dut_flaga GPIO state.
+ bool dutflaga_gpio_state;
+ if (!GetDutflagGpioStatus(kDutflagaGpio, &dutflaga_gpio_state)) {
+ LOG(WARNING) << "dutflaga GPIO reading failed, defaulting to non-test mode";
+ return false;
+ }
+
+ LOG(INFO) << "dutflaga GPIO reading: "
+ << (dutflaga_gpio_state ? "on (non-test mode)" : "off (test mode)");
+ return !dutflaga_gpio_state;
+}
+
+bool GpioHandler::GetDutflagGpioDevName(struct udev* udev,
+ const string& gpio_dutflag_str,
+ const char** dutflag_dev_name_p) {
+ CHECK(udev && dutflag_dev_name_p);
+
+ struct udev_enumerate* udev_enum = NULL;
+ int num_gpio_dutflags = 0;
+ const string gpio_dutflag_pattern = "*" + gpio_dutflag_str;
+ int ret;
+
+ // Initialize udev enumerate context and closer.
+ if (!(udev_enum = udev_enumerate_new(udev))) {
+ LOG(ERROR) << "failed to obtain udev enumerate context";
+ return false;
+ }
+ ScopedUdevEnumerateCloser udev_enum_closer(&udev_enum);
+
+ // Populate filters for find an initialized GPIO chip.
+ if ((ret = udev_enumerate_add_match_subsystem(udev_enum, "gpio")) ||
+ (ret = udev_enumerate_add_match_sysname(udev_enum,
+ gpio_dutflag_pattern.c_str()))) {
+ LOG(ERROR) << "failed to initialize udev enumerate context (" << ret << ")";
+ return false;
+ }
+
+ // Obtain list of matching devices.
+ if ((ret = udev_enumerate_scan_devices(udev_enum))) {
+ LOG(ERROR) << "udev enumerate context scan failed (error code "
+ << ret << ")";
+ return false;
+ }
+
+ // Iterate over matching devices, obtain GPIO dut_flaga identifier.
+ struct udev_list_entry* list_entry;
+ udev_list_entry_foreach(list_entry,
+ udev_enumerate_get_list_entry(udev_enum)) {
+ // Make sure we're not enumerating more than one device.
+ num_gpio_dutflags++;
+ if (num_gpio_dutflags > 1) {
+ LOG(WARNING) <<
+ "enumerated multiple dutflag GPIOs, ignoring this one";
+ continue;
+ }
+
+ // Obtain device name.
+ const char* dev_name = udev_list_entry_get_name(list_entry);
+ if (!dev_name) {
+ LOG(WARNING) << "enumerated device has a null name string, skipping";
+ continue;
+ }
+
+ // Obtain device object.
+ struct udev_device* dev = udev_device_new_from_syspath(udev, dev_name);
+ if (!dev) {
+ LOG(WARNING) <<
+ "obtained a null device object for enumerated device, skipping";
+ continue;
+ }
+
+ // Obtain device syspath.
+ const char* dev_syspath = udev_device_get_syspath(dev);
+ if (dev_syspath) {
+ LOG(INFO) << "obtained device syspath: " << dev_syspath;
+ *dutflag_dev_name_p = strdup(dev_syspath);
+ } else {
+ LOG(WARNING) << "could not obtain device syspath";
+ }
+
+ udev_device_unref(dev);
+ }
+
+ return true;
+}
+
+bool GpioHandler::GetDutflagGpioDevNames(string* dutflaga_dev_name_p,
+ string* dutflagb_dev_name_p) {
+ if (!(dutflaga_dev_name_p || dutflagb_dev_name_p))
+ return true; // No output pointers, nothing to do.
+
+ string gpio_dutflaga_str, gpio_dutflagb_str;
+
+ if (!(dutflaga_dev_name_ && dutflagb_dev_name_)) {
+ struct udev* udev = NULL;
+ struct udev_enumerate* udev_enum = NULL;
+ int num_gpio_chips = 0;
+ const char* id_gpio_dutflaga = NULL;
+ const char* id_gpio_dutflagb = NULL;
+ int ret;
+
+ LOG(INFO) << "begin discovery of dut_flaga/b devices";
+
+ // Obtain libudev instance and closer.
+ if (!(udev = udev_new())) {
+ LOG(ERROR) << "failed to obtain libudev instance";
+ return false;
+ }
+ ScopedUdevCloser udev_closer(&udev);
+
+ // Initialize a udev enumerate object and closer with a bounded lifespan.
+ {
+ if (!(udev_enum = udev_enumerate_new(udev))) {
+ LOG(ERROR) << "failed to obtain udev enumerate context";
+ return false;
+ }
+ ScopedUdevEnumerateCloser udev_enum_closer(&udev_enum);
+
+ // Populate filters for find an initialized GPIO chip.
+ if ((ret = udev_enumerate_add_match_subsystem(udev_enum, "gpio")) ||
+ (ret = udev_enumerate_add_match_sysname(udev_enum, "gpiochip*")) ||
+ (ret = udev_enumerate_add_match_property(udev_enum,
+ kIdGpioDutflaga, "*")) ||
+ (ret = udev_enumerate_add_match_property(udev_enum,
+ kIdGpioDutflagb, "*"))) {
+ LOG(ERROR) << "failed to initialize udev enumerate context ("
+ << ret << ")";
+ return false;
+ }
+
+ // Obtain list of matching devices.
+ if ((ret = udev_enumerate_scan_devices(udev_enum))) {
+ LOG(ERROR) << "udev enumerate context scan failed (" << ret << ")";
+ return false;
+ }
+
+ // Iterate over matching devices, obtain GPIO dut_flaga identifier.
+ struct udev_list_entry* list_entry;
+ udev_list_entry_foreach(list_entry,
+ udev_enumerate_get_list_entry(udev_enum)) {
+ // Make sure we're not enumerating more than one device.
+ num_gpio_chips++;
+ if (num_gpio_chips > 1) {
+ LOG(WARNING) << "enumerated multiple GPIO chips, ignoring this one";
+ continue;
+ }
+
+ // Obtain device name.
+ const char* dev_name = udev_list_entry_get_name(list_entry);
+ if (!dev_name) {
+ LOG(WARNING) << "enumerated device has a null name string, skipping";
+ continue;
+ }
+
+ // Obtain device object.
+ struct udev_device* dev = udev_device_new_from_syspath(udev, dev_name);
+ if (!dev) {
+ LOG(WARNING) <<
+ "obtained a null device object for enumerated device, skipping";
+ continue;
+ }
+
+ // Obtain dut_flaga/b identifiers.
+ id_gpio_dutflaga =
+ udev_device_get_property_value(dev, kIdGpioDutflaga);
+ id_gpio_dutflagb =
+ udev_device_get_property_value(dev, kIdGpioDutflagb);
+ if (id_gpio_dutflaga && id_gpio_dutflagb) {
+ LOG(INFO) << "found dut_flaga/b identifiers: a=" << id_gpio_dutflaga
+ << " b=" << id_gpio_dutflagb;
+
+ gpio_dutflaga_str = id_gpio_dutflaga;
+ gpio_dutflagb_str = id_gpio_dutflagb;
+ } else {
+ LOG(ERROR) << "GPIO chip missing dut_flaga/b properties";
+ }
+
+ udev_device_unref(dev);
+ }
+ }
+
+ // Obtain dut_flaga, reusing the same udev instance.
+ if (!dutflaga_dev_name_ && !gpio_dutflaga_str.empty()) {
+ LOG(INFO) << "discovering device for GPIO dut_flaga ";
+ if (!GetDutflagGpioDevName(udev, gpio_dutflaga_str,
+ &dutflaga_dev_name_)) {
+ LOG(ERROR) << "discovery of dut_flaga GPIO device failed";
+ return false;
+ }
+ }
+
+ // Now obtain dut_flagb.
+ if (!dutflagb_dev_name_ && !gpio_dutflagb_str.empty()) {
+ LOG(INFO) << "discovering device for GPIO dut_flagb";
+ if (!GetDutflagGpioDevName(udev, gpio_dutflagb_str,
+ &dutflagb_dev_name_)) {
+ LOG(ERROR) << "discovery of dut_flagb GPIO device failed";
+ return false;
+ }
+ }
+
+ LOG(INFO) << "end discovery of dut_flaga/b devices";
+ }
+
+ // Write cached GPIO dutflag(s) to output strings.
+ if (dutflaga_dev_name_p && dutflaga_dev_name_)
+ *dutflaga_dev_name_p = dutflaga_dev_name_;
+ if (dutflagb_dev_name_p && dutflagb_dev_name_)
+ *dutflagb_dev_name_p = dutflagb_dev_name_;
+
+ return true;
+}
+
+bool GpioHandler::GetDutflagGpioStatus(GpioHandler::DutflagGpioId id,
+ bool* status_p) {
+ CHECK(status_p);
+
+ // Obtain GPIO device file name.
+ string dutflag_dev_name;
+ switch (id) {
+ case kDutflagaGpio:
+ GetDutflagGpioDevNames(&dutflag_dev_name, NULL);
+ break;
+ case kDutflagbGpio:
+ GetDutflagGpioDevNames(NULL, &dutflag_dev_name);
+ break;
+ default:
+ LOG(FATAL) << "invalid dutflag GPIO id: " << id;
+ }
+
+ if (dutflag_dev_name.empty()) {
+ LOG(WARNING) << "could not find dutflag GPIO device";
+ return false;
+ }
+
+ // Open device for reading.
+ string dutflaga_value_dev_name = dutflag_dev_name + "/value";
+ int dutflaga_fd = HANDLE_EINTR(open(dutflaga_value_dev_name.c_str(), 0));
+ if (dutflaga_fd < 0) {
+ PLOG(ERROR) << "opening dutflaga GPIO device file failed";
+ return false;
+ }
+ ScopedEintrSafeFdCloser dutflaga_fd_closer(&dutflaga_fd);
+
+ // Read the dut_flaga GPIO signal. We attempt to read more than---but expect
+ // to receive exactly---two characters: a '0' or '1', and a newline. This is
+ // to ensure that the GPIO device returns a legible result.
+ char buf[3];
+ int ret = HANDLE_EINTR(read(dutflaga_fd, buf, 3));
+ if (ret != 2) {
+ if (ret < 0)
+ PLOG(ERROR) << "reading dutflaga GPIO status failed";
+ else
+ LOG(ERROR) << "read more than one byte (" << ret << ")";
+ return false;
+ }
+
+ // Identify and write GPIO status.
+ char c = buf[0];
+ if ((c == '0' || c == '1') && buf[1] == '\n') {
+ *status_p = (c == '1');
+ } else {
+ buf[2] = '\0';
+ LOG(ERROR) << "read unexpected value from dutflaga GPIO: " << buf;
+ return false;
+ }
+
+ return true;
+}
+
+} // namespace chromeos_update_engine
diff --git a/gpio_handler.h b/gpio_handler.h
new file mode 100644
index 0000000..c3d5a38
--- /dev/null
+++ b/gpio_handler.h
@@ -0,0 +1,71 @@
+// Copyright (c) 2012 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.
+
+#ifndef CHROMEOS_PLATFORM_UPDATE_ENGINE_GPIO_HANDLER_H__
+#define CHROMEOS_PLATFORM_UPDATE_ENGINE_GPIO_HANDLER_H__
+
+#include <libudev.h>
+
+#include <string>
+
+#include <base/basictypes.h>
+
+namespace chromeos_update_engine {
+
+// Handles GPIO signals, which are used for indicating a lab test environment.
+// This class detects, reads and decides whether a test scenario is in effect,
+// which can be used by client code to trigger test-specific behavior. This
+// class has only static members/methods and cannot be instantiated.
+class GpioHandler {
+ public:
+ // Returns true iff GPIOs have been signaled to indicate an automated test
+ // case. This triggers a discovery and reading of the dutflaga/b GPIOs.
+ static bool IsGpioSignalingTest();
+
+ private:
+ // This class cannot be instantiated.
+ GpioHandler() {}
+
+ // Enumerator for dutflag GPIOs.
+ enum DutflagGpioId {
+ kDutflagaGpio,
+ kDutflagbGpio,
+ };
+
+ // Gets the fully qualified sysfs name of a dutflag device. |udev| is a live
+ // libudev instance; |gpio_dutflag_str| is the identifier for the requested
+ // dutflag GPIO. The output is stored in the string pointed to by
+ // |dutflag_dev_name_p|. Returns true upon success, false otherwise.
+ static bool GetDutflagGpioDevName(struct udev* udev,
+ const std::string& gpio_dutflag_str,
+ const char** dutflag_dev_name_p);
+
+ // Gets the dut_flaga/b GPIO device names and copies them into the two string
+ // arguments, respectively. A string pointer may be null, in which case
+ // discovery will not be attempted for the corresponding device. The function
+ // caches these strings, which are assumed to be hardware constants. Returns
+ // true upon success, false otherwise.
+ static bool GetDutflagGpioDevNames(std::string* dutflaga_dev_name_p,
+ std::string* dutflagb_dev_name_p);
+
+ // Writes the dut_flaga GPIO status into its argument, where true/false stand
+ // for "on"/"off", respectively. Returns true upon success, false otherwise
+ // (in which case no value is written to |status|).
+ static bool GetDutflagaGpio(bool* status);
+
+ // Reads the value of a dut_flag GPIO |id| and stores it in |status_p|.
+ // Returns true upon success, false otherwise (which also means that the GPIO
+ // value was not stored and should not be used).
+ static bool GetDutflagGpioStatus(DutflagGpioId id, bool* status_p);
+
+ // Dutflaga/b GPIO device names.
+ static const char* dutflaga_dev_name_;
+ static const char* dutflagb_dev_name_;
+
+ DISALLOW_COPY_AND_ASSIGN(GpioHandler);
+};
+
+} // namespace chromeos_update_engine
+
+#endif // CHROMEOS_PLATFORM_UPDATE_ENGINE_GPIO_HANDLER_H__
diff --git a/update_attempter.cc b/update_attempter.cc
index bdf0105..b2f6c6e 100644
--- a/update_attempter.cc
+++ b/update_attempter.cc
@@ -14,9 +14,7 @@
#include <tr1/memory>
#include <vector>
-#include <base/eintr_wrapper.h>
#include <base/rand_util.h>
-#include <base/string_util.h>
#include <glib.h>
#include <metrics/metrics_library.h>
#include <policy/libpolicy.h>
@@ -48,54 +46,16 @@
const int UpdateAttempter::kMaxDeltaUpdateFailures = 3;
+// Private test server URL w/ custom port number.
// TODO(garnold) this is currently an arbitrary address and will change based on
// discussion about the actual test lab configuration.
-const char* const UpdateAttempter::kTestUpdateUrl("https://10.0.0.1/update");
+const char* const UpdateAttempter::kTestUpdateUrl("https://10.0.0.1:70529/update");
const char* kUpdateCompletedMarker =
"/var/run/update_engine_autoupdate_completed";
namespace {
const int kMaxConsecutiveObeyProxyRequests = 20;
-
-// Names of udev properties that are linked to the GPIO chip device and identify
-// the two dutflag GPIOs on different boards.
-const char kIdGpioDutflaga[] = "ID_GPIO_DUTFLAGA";
-const char kIdGpioDutflagb[] = "ID_GPIO_DUTFLAGB";
-
-// Scoped closer for udev and udev_enumerate objects.
-// TODO(garnold) chromium-os:26934: it would be nice to generalize the different
-// ScopedFooCloser implementations in update engine using a single template.
-class ScopedUdevCloser {
- public:
- explicit ScopedUdevCloser(udev **udev_p) : udev_p_(udev_p) {}
- ~ScopedUdevCloser() {
- if (udev_p_ && *udev_p_) {
- udev_unref(*udev_p_);
- *udev_p_ = NULL;
- }
- }
- private:
- struct udev **udev_p_;
-
- DISALLOW_COPY_AND_ASSIGN(ScopedUdevCloser);
-};
-
-class ScopedUdevEnumerateCloser {
- public:
- explicit ScopedUdevEnumerateCloser(udev_enumerate **udev_enum_p) :
- udev_enum_p_(udev_enum_p) {}
- ~ScopedUdevEnumerateCloser() {
- if (udev_enum_p_ && *udev_enum_p_) {
- udev_enumerate_unref(*udev_enum_p_);
- *udev_enum_p_ = NULL;
- }
- }
- private:
- struct udev_enumerate **udev_enum_p_;
-
- DISALLOW_COPY_AND_ASSIGN(ScopedUdevEnumerateCloser);
-};
} // namespace {}
const char* UpdateStatusToString(UpdateStatus status) {
@@ -167,7 +127,8 @@
updated_boot_flags_(false),
update_boot_flags_running_(false),
start_action_processor_(false),
- policy_provider_(NULL) {
+ policy_provider_(NULL),
+ is_using_test_url_(false) {
if (utils::FileExists(kUpdateCompletedMarker))
status_ = UPDATE_STATUS_UPDATED_NEED_REBOOT;
}
@@ -179,7 +140,8 @@
void UpdateAttempter::Update(const string& app_version,
const string& omaha_url,
bool obey_proxies,
- bool interactive) {
+ bool interactive,
+ bool is_test) {
chrome_proxy_resolver_.Init();
fake_update_success_ = false;
if (status_ == UPDATE_STATUS_UPDATED_NEED_REBOOT) {
@@ -208,23 +170,11 @@
if (policy_provider_->device_policy_is_loaded())
policy_provider_->GetDevicePolicy().GetReleaseChannel(&release_track);
- // Force alternate default address for automated test case, based on GPIO
- // signal. We replicate the URL string so as not to overwrite the argument.
+ // Determine whether an alternative test address should be used.
string omaha_url_to_use = omaha_url;
- if (omaha_url_to_use.empty()) {
- bool dutflaga_gpio_status;
- if (GetDutflagaGpio(&dutflaga_gpio_status)) {
- LOG(INFO) << "dutflaga GPIO status: "
- << (dutflaga_gpio_status ? "on" : "off");
-
- // The dut_flaga GPIO is actually signaled when in the 'off' position.
- if (!dutflaga_gpio_status) {
- LOG(INFO) << "using alternative server address: " << kTestUpdateUrl;
- omaha_url_to_use = kTestUpdateUrl;
- }
- } else {
- LOG(ERROR) << "reading dutflaga GPIO status failed";
- }
+ if ((is_using_test_url_ = (omaha_url_to_use.empty() && is_test))) {
+ omaha_url_to_use = kTestUpdateUrl;
+ LOG(INFO) << "using alternative server address: " << omaha_url_to_use;
}
if (!omaha_request_params_.Init(app_version, omaha_url_to_use,
@@ -348,7 +298,8 @@
BondActions(kernel_filesystem_verifier_action.get(),
postinstall_runner_action.get());
- SetStatusAndNotify(UPDATE_STATUS_CHECKING_FOR_UPDATE);
+ SetStatusAndNotify(UPDATE_STATUS_CHECKING_FOR_UPDATE,
+ kUpdateNoticeUnspecified);
// Just in case we didn't update boot flags yet, make sure they're updated
// before any update processing starts.
@@ -363,7 +314,18 @@
<< UpdateStatusToString(status_) << ", so not checking.";
return;
}
- Update(app_version, omaha_url, true, true);
+
+ // Read GPIO signals and determine whether this is an automated test scenario.
+ // For safety, we only allow a test update to be performed once; subsequent
+ // update requests will be carried out normally.
+ static bool is_test_used_once = false;
+ bool is_test = !is_test_used_once && GpioHandler::IsGpioSignalingTest();
+ if (is_test) {
+ LOG(INFO) << "test mode signaled";
+ is_test_used_once = true;
+ }
+
+ Update(app_version, omaha_url, true, true, is_test);
}
bool UpdateAttempter::RebootIfNeeded() {
@@ -388,7 +350,13 @@
if (status_ == UPDATE_STATUS_REPORTING_ERROR_EVENT) {
LOG(INFO) << "Error event sent.";
- SetStatusAndNotify(UPDATE_STATUS_IDLE);
+
+ // Inform scheduler of new status; also specifically inform about a failed
+ // update attempt with a test address.
+ SetStatusAndNotify(UPDATE_STATUS_IDLE,
+ (is_using_test_url_ ? kUpdateNoticeTestAddrFailed :
+ kUpdateNoticeUnspecified));
+
if (!fake_update_success_) {
return;
}
@@ -401,7 +369,8 @@
prefs_->SetInt64(kPrefsDeltaUpdateFailures, 0);
prefs_->SetString(kPrefsPreviousVersion, omaha_request_params_.app_version);
DeltaPerformer::ResetUpdateProgress(prefs_, false);
- SetStatusAndNotify(UPDATE_STATUS_UPDATED_NEED_REBOOT);
+ SetStatusAndNotify(UPDATE_STATUS_UPDATED_NEED_REBOOT,
+ kUpdateNoticeUnspecified);
// Report the time it took to update the system.
int64_t update_time = time(NULL) - last_checked_time_;
@@ -418,14 +387,14 @@
return;
}
LOG(INFO) << "No update.";
- SetStatusAndNotify(UPDATE_STATUS_IDLE);
+ SetStatusAndNotify(UPDATE_STATUS_IDLE, kUpdateNoticeUnspecified);
}
void UpdateAttempter::ProcessingStopped(const ActionProcessor* processor) {
// Reset process priority back to normal.
CleanupPriorityManagement();
download_progress_ = 0.0;
- SetStatusAndNotify(UPDATE_STATUS_IDLE);
+ SetStatusAndNotify(UPDATE_STATUS_IDLE, kUpdateNoticeUnspecified);
actions_.clear();
error_event_.reset(NULL);
}
@@ -484,9 +453,10 @@
new_size_ = plan.size;
SetupDownload();
SetupPriorityManagement();
- SetStatusAndNotify(UPDATE_STATUS_UPDATE_AVAILABLE);
+ SetStatusAndNotify(UPDATE_STATUS_UPDATE_AVAILABLE,
+ kUpdateNoticeUnspecified);
} else if (type == DownloadAction::StaticType()) {
- SetStatusAndNotify(UPDATE_STATUS_FINALIZING);
+ SetStatusAndNotify(UPDATE_STATUS_FINALIZING, kUpdateNoticeUnspecified);
}
}
@@ -523,7 +493,7 @@
progress - download_progress_ >= kDeltaPercent ||
TimeTicks::Now() - last_notify_time_ >= TimeDelta::FromSeconds(10)) {
download_progress_ = progress;
- SetStatusAndNotify(UPDATE_STATUS_DOWNLOADING);
+ SetStatusAndNotify(UPDATE_STATUS_DOWNLOADING, kUpdateNoticeUnspecified);
}
}
@@ -540,259 +510,6 @@
return true;
}
-// Discovers the dut_flag GPIO identified by |gpio_dutflag_str| and stores the
-// full device name in |dutflag_dev_name_p|. The function uses an open libudev
-// instance |udev|. Returns zero on success, -1 otherwise.
-bool UpdateAttempter::GetDutflagGpioDevName(struct udev* udev,
- const string& gpio_dutflag_str,
- string* dutflag_dev_name_p) {
- CHECK(udev && dutflag_dev_name_p);
-
- struct udev_enumerate* udev_enum = NULL;
- int num_gpio_dutflags = 0;
- const string gpio_dutflag_pattern = "*" + gpio_dutflag_str;
- int ret;
-
- // Initialize udev enumerate context and closer.
- if (!(udev_enum = udev_enumerate_new(udev))) {
- LOG(ERROR) << "failed to obtain udev enumerate context";
- return false;
- }
- ScopedUdevEnumerateCloser udev_enum_closer(&udev_enum);
-
- // Populate filters for find an initialized GPIO chip.
- if ((ret = udev_enumerate_add_match_subsystem(udev_enum, "gpio")) ||
- (ret = udev_enumerate_add_match_sysname(udev_enum,
- gpio_dutflag_pattern.c_str()))) {
- LOG(ERROR) << "failed to initialize udev enumerate context (" << ret << ")";
- return false;
- }
-
- // Obtain list of matching devices.
- if ((ret = udev_enumerate_scan_devices(udev_enum))) {
- LOG(ERROR) << "udev enumerate context scan failed (error code "
- << ret << ")";
- return false;
- }
-
- // Iterate over matching devices, obtain GPIO dut_flaga identifier.
- struct udev_list_entry* list_entry;
- udev_list_entry_foreach(list_entry,
- udev_enumerate_get_list_entry(udev_enum)) {
- // Make sure we're not enumerating more than one device.
- num_gpio_dutflags++;
- if (num_gpio_dutflags > 1) {
- LOG(WARNING) <<
- "enumerated multiple dutflag GPIOs, ignoring this one";
- continue;
- }
-
- // Obtain device name.
- const char* dev_name = udev_list_entry_get_name(list_entry);
- if (!dev_name) {
- LOG(WARNING) << "enumerated device has a null name string, skipping";
- continue;
- }
-
- // Obtain device object.
- struct udev_device* dev = udev_device_new_from_syspath(udev, dev_name);
- if (!dev) {
- LOG(WARNING) <<
- "obtained a null device object for enumerated device, skipping";
- continue;
- }
-
- // Obtain device syspath.
- const char* dev_syspath = udev_device_get_syspath(dev);
- if (dev_syspath) {
- LOG(INFO) << "obtained device syspath: " << dev_syspath;
- *dutflag_dev_name_p = dev_syspath;
- } else {
- LOG(WARNING) << "could not obtain device syspath";
- }
-
- udev_device_unref(dev);
- }
-
- return true;
-}
-
-// Discovers and stores the device names of the two dut_flag GPIOs. Returns zero
-// upon success, -1 otherwise.
-bool UpdateAttempter::GetDutflagGpioDevNames(string* dutflaga_dev_name_p,
- string* dutflagb_dev_name_p) {
- if (!(dutflaga_dev_name_p || dutflagb_dev_name_p))
- return true; // No output pointers, nothing to do.
-
- string gpio_dutflaga_str, gpio_dutflagb_str;
-
- if (dutflaga_dev_name_.empty() || dutflagb_dev_name_.empty()) {
- struct udev* udev = NULL;
- struct udev_enumerate* udev_enum = NULL;
- int num_gpio_chips = 0;
- const char* id_gpio_dutflaga = NULL;
- const char* id_gpio_dutflagb = NULL;
- int ret;
-
- LOG(INFO) << "begin discovery of dut_flaga/b devices";
-
- // Obtain libudev instance and closer.
- if (!(udev = udev_new())) {
- LOG(ERROR) << "failed to obtain libudev instance";
- return false;
- }
- ScopedUdevCloser udev_closer(&udev);
-
- // Initialize a udev enumerate object and closer with a bounded lifespan.
- {
- if (!(udev_enum = udev_enumerate_new(udev))) {
- LOG(ERROR) << "failed to obtain udev enumerate context";
- return false;
- }
- ScopedUdevEnumerateCloser udev_enum_closer(&udev_enum);
-
- // Populate filters for find an initialized GPIO chip.
- if ((ret = udev_enumerate_add_match_subsystem(udev_enum, "gpio")) ||
- (ret = udev_enumerate_add_match_sysname(udev_enum, "gpiochip*")) ||
- (ret = udev_enumerate_add_match_property(udev_enum,
- kIdGpioDutflaga, "*")) ||
- (ret = udev_enumerate_add_match_property(udev_enum,
- kIdGpioDutflagb, "*"))) {
- LOG(ERROR) << "failed to initialize udev enumerate context ("
- << ret << ")";
- return false;
- }
-
- // Obtain list of matching devices.
- if ((ret = udev_enumerate_scan_devices(udev_enum))) {
- LOG(ERROR) << "udev enumerate context scan failed (" << ret << ")";
- return false;
- }
-
- // Iterate over matching devices, obtain GPIO dut_flaga identifier.
- struct udev_list_entry* list_entry;
- udev_list_entry_foreach(list_entry,
- udev_enumerate_get_list_entry(udev_enum)) {
- // Make sure we're not enumerating more than one device.
- num_gpio_chips++;
- if (num_gpio_chips > 1) {
- LOG(WARNING) << "enumerated multiple GPIO chips, ignoring this one";
- continue;
- }
-
- // Obtain device name.
- const char* dev_name = udev_list_entry_get_name(list_entry);
- if (!dev_name) {
- LOG(WARNING) << "enumerated device has a null name string, skipping";
- continue;
- }
-
- // Obtain device object.
- struct udev_device* dev = udev_device_new_from_syspath(udev, dev_name);
- if (!dev) {
- LOG(WARNING) <<
- "obtained a null device object for enumerated device, skipping";
- continue;
- }
-
- // Obtain dut_flaga/b identifiers.
- id_gpio_dutflaga =
- udev_device_get_property_value(dev, kIdGpioDutflaga);
- id_gpio_dutflagb =
- udev_device_get_property_value(dev, kIdGpioDutflagb);
- if (id_gpio_dutflaga && id_gpio_dutflagb) {
- LOG(INFO) << "found dut_flaga/b identifiers: a=" << id_gpio_dutflaga
- << " b=" << id_gpio_dutflagb;
-
- gpio_dutflaga_str = id_gpio_dutflaga;
- gpio_dutflagb_str = id_gpio_dutflagb;
- } else {
- LOG(ERROR) << "GPIO chip missing dut_flaga/b properties";
- }
-
- udev_device_unref(dev);
- }
- }
-
- // Obtain dut_flaga, reusing the same udev instance.
- if (dutflaga_dev_name_.empty() && !gpio_dutflaga_str.empty()) {
- LOG(INFO) << "discovering device for GPIO dut_flaga ";
- if (!GetDutflagGpioDevName(udev, gpio_dutflaga_str,
- &dutflaga_dev_name_)) {
- LOG(ERROR) << "discovery of dut_flaga GPIO device failed";
- return false;
- }
- }
-
- // Now obtain dut_flagb.
- if (dutflagb_dev_name_.empty() && !gpio_dutflagb_str.empty()) {
- LOG(INFO) << "discovering device for GPIO dut_flagb";
- if (!GetDutflagGpioDevName(udev, gpio_dutflagb_str,
- &dutflagb_dev_name_)) {
- LOG(ERROR) << "discovery of dut_flagb GPIO device failed";
- return false;
- }
- }
-
- LOG(INFO) << "end discovery of dut_flaga/b devices";
- }
-
- // Write cached GPIO dutflag(s) to output strings.
- if (dutflaga_dev_name_p)
- *dutflaga_dev_name_p = dutflaga_dev_name_;
- if (dutflagb_dev_name_p)
- *dutflagb_dev_name_p = dutflagb_dev_name_;
-
- return true;
-}
-
-// Reads the value of the dut_flaga GPIO and stores it in |status_p|. Returns
-// true upon success, false otherwise (which also means that the GPIO value was
-// not stored and should not be used).
-bool UpdateAttempter::GetDutflagaGpio(bool* status_p) {
- // Obtain GPIO device file name.
- string dutflaga_dev_name;
- GetDutflagGpioDevNames(&dutflaga_dev_name, NULL);
- if (dutflaga_dev_name.empty()) {
- LOG(WARNING) << "could not find dutflaga GPIO device";
- return false;
- }
-
- // Open device for reading.
- string dutflaga_value_dev_name = dutflaga_dev_name + "/value";
- int dutflaga_fd = HANDLE_EINTR(open(dutflaga_value_dev_name.c_str(), 0));
- if (dutflaga_fd < 0) {
- PLOG(ERROR) << "opening dutflaga GPIO device file failed";
- return false;
- }
- ScopedEintrSafeFdCloser dutflaga_fd_closer(&dutflaga_fd);
-
- // Read the dut_flaga GPIO signal. We attempt to read more than---but expect
- // to receive exactly---two characters: a '0' or '1', and a newline. This is
- // to ensure that the GPIO device returns a legible result.
- char buf[3];
- int ret = HANDLE_EINTR(read(dutflaga_fd, buf, 3));
- if (ret != 2) {
- if (ret < 0)
- PLOG(ERROR) << "reading dutflaga GPIO status failed";
- else
- LOG(ERROR) << "read more than one byte (" << ret << ")";
- return false;
- }
-
- // Identify and write GPIO status.
- char c = buf[0];
- if ((c == '0' || c == '1') && buf[1] == '\n') {
- *status_p = (c == '1');
- } else {
- buf[2] = '\0';
- LOG(ERROR) << "read unexpected value from dutflaga GPIO: " << buf;
- return false;
- }
-
- return true;
-}
-
void UpdateAttempter::UpdateBootFlags() {
if (update_boot_flags_running_) {
LOG(INFO) << "Update boot flags running, nothing to do.";
@@ -845,10 +562,11 @@
new_size_);
}
-void UpdateAttempter::SetStatusAndNotify(UpdateStatus status) {
+void UpdateAttempter::SetStatusAndNotify(UpdateStatus status,
+ UpdateNotice notice) {
status_ = status;
if (update_check_scheduler_) {
- update_check_scheduler_->SetUpdateStatus(status_);
+ update_check_scheduler_->SetUpdateStatus(status_, notice);
}
BroadcastStatus();
}
@@ -900,7 +618,8 @@
false));
actions_.push_back(shared_ptr<AbstractAction>(error_event_action));
processor_->EnqueueAction(error_event_action.get());
- SetStatusAndNotify(UPDATE_STATUS_REPORTING_ERROR_EVENT);
+ SetStatusAndNotify(UPDATE_STATUS_REPORTING_ERROR_EVENT,
+ kUpdateNoticeUnspecified);
processor_->StartProcessing();
return true;
}
@@ -1030,7 +749,8 @@
}
// Update the status which will schedule the next update check
- SetStatusAndNotify(UPDATE_STATUS_UPDATED_NEED_REBOOT);
+ SetStatusAndNotify(UPDATE_STATUS_UPDATED_NEED_REBOOT,
+ kUpdateNoticeUnspecified);
}
} // namespace chromeos_update_engine
diff --git a/update_attempter.h b/update_attempter.h
index a1ae772..4f30667 100644
--- a/update_attempter.h
+++ b/update_attempter.h
@@ -14,11 +14,11 @@
#include <base/time.h>
#include <glib.h>
#include <gtest/gtest_prod.h> // for FRIEND_TEST
-#include <libudev.h>
#include "update_engine/action_processor.h"
#include "update_engine/chrome_browser_proxy_resolver.h"
#include "update_engine/download_action.h"
+#include "update_engine/gpio_handler.h"
#include "update_engine/omaha_request_params.h"
#include "update_engine/omaha_response_handler_action.h"
#include "update_engine/proxy_resolver.h"
@@ -47,6 +47,11 @@
UPDATE_STATUS_REPORTING_ERROR_EVENT,
};
+enum UpdateNotice {
+ kUpdateNoticeUnspecified = 0,
+ kUpdateNoticeTestAddrFailed,
+};
+
const char* UpdateStatusToString(UpdateStatus status);
class UpdateAttempter : public ActionProcessorDelegate,
@@ -59,16 +64,18 @@
DbusGlibInterface* dbus_iface);
virtual ~UpdateAttempter();
- // Checks for update and, if a newer version is available, attempts
- // to update the system. Non-empty |in_app_version| or
- // |in_update_url| prevents automatic detection of the parameter.
- // If |obey_proxies| is true, the update will likely respect Chrome's
- // proxy setting. For security reasons, we may still not honor them.
- // Interactive should be true if this was called from the user (ie dbus).
+ // Checks for update and, if a newer version is available, attempts to update
+ // the system. Non-empty |in_app_version| or |in_update_url| prevents
+ // automatic detection of the parameter. If |obey_proxies| is true, the
+ // update will likely respect Chrome's proxy setting. For security reasons, we
+ // may still not honor them. Interactive should be true if this was called
+ // from the user (ie dbus). |is_test| will lead to using an alternative test
+ // server URL, if |omaha_url| is empty.
virtual void Update(const std::string& app_version,
const std::string& omaha_url,
bool obey_proxies,
- bool interactive);
+ bool interactive,
+ bool is_test);
// ActionProcessorDelegate methods:
void ProcessingDone(const ActionProcessor* processor, ActionExitCode code);
@@ -154,9 +161,10 @@
FRIEND_TEST(UpdateAttempterTest, ScheduleErrorEventActionTest);
FRIEND_TEST(UpdateAttempterTest, UpdateTest);
- // Sets the status to the given status and notifies a status update
- // over dbus.
- void SetStatusAndNotify(UpdateStatus status);
+ // Sets the status to the given status and notifies a status update over dbus.
+ // Also accepts a supplement notice, which is delegated to the scheduler and
+ // used for making better informed scheduling decisions (e.g. retry timeout).
+ void SetStatusAndNotify(UpdateStatus status, UpdateNotice notice);
// Sets up the download parameters after receiving the update check response.
void SetupDownload();
@@ -216,26 +224,6 @@
// update has been applied.
void PingOmaha();
- // Gets the fully qualified sysfs name of a dutflag device. |udev| is a live
- // libudev instance; |gpio_dutflag_str| is the identifier for the requested
- // dutflag GPIO. The output is stored in the string pointed to by
- // |dutflag_dev_name_p|. Returns true upon success, false otherwise.
- bool GetDutflagGpioDevName(struct udev* udev,
- const std::string& gpio_dutflag_str,
- std::string* dutflag_dev_name_p);
-
- // Gets the dut_flaga/b GPIO device names and copies them into the two string
- // arguments, respectively. The function caches these strings, which are
- // assumed to be hardware constants. Returns true upon success, false
- // otherwise.
- bool GetDutflagGpioDevNames(std::string* dutflaga_dev_name_p,
- std::string* dutflagb_dev_name_p);
-
- // Writes the dut_flaga GPIO status into its argument, where true/false stand
- // for "on"/"off", respectively. Returns true upon success, false otherwise
- // (in which case no value is written to |status|).
- bool GetDutflagaGpio(bool* status);
-
// 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.
@@ -317,9 +305,8 @@
// Used for fetching information about the device policy.
scoped_ptr<policy::PolicyProvider> policy_provider_;
- // Dutflaga/b GPIO device names.
- std::string dutflaga_dev_name_;
- std::string dutflagb_dev_name_;
+ // A flag for indicating whether we are using a test server URL.
+ bool is_using_test_url_;
DISALLOW_COPY_AND_ASSIGN(UpdateAttempter);
};
diff --git a/update_attempter_mock.h b/update_attempter_mock.h
index 21fa4a3..50b5c25 100644
--- a/update_attempter_mock.h
+++ b/update_attempter_mock.h
@@ -17,10 +17,11 @@
explicit UpdateAttempterMock(MockDbusGlib* dbus)
: UpdateAttempter(NULL, NULL, dbus) {}
- MOCK_METHOD4(Update, void(const std::string& app_version,
+ MOCK_METHOD5(Update, void(const std::string& app_version,
const std::string& omaha_url,
bool obey_proxies,
- bool interactive));
+ bool interactive,
+ bool is_test));
};
} // namespace chromeos_update_engine
diff --git a/update_attempter_unittest.cc b/update_attempter_unittest.cc
index 6c5b5a2..463d5a6 100644
--- a/update_attempter_unittest.cc
+++ b/update_attempter_unittest.cc
@@ -298,7 +298,7 @@
}
EXPECT_CALL(*processor_, StartProcessing()).Times(1);
- attempter_.Update("", "", false, false);
+ attempter_.Update("", "", false, false, false);
g_idle_add(&StaticUpdateTestVerify, this);
}
@@ -398,7 +398,7 @@
SetArgumentPointee<0>(std::string("canary-channel")),
Return(true)));
- attempter_.Update("", "", false, false);
+ attempter_.Update("", "", false, false, false);
EXPECT_EQ("canary-channel", attempter_.omaha_request_params_.app_track);
g_idle_add(&StaticQuitMainLoop, this);
diff --git a/update_check_scheduler.cc b/update_check_scheduler.cc
index 0218393..4d088ab 100644
--- a/update_check_scheduler.cc
+++ b/update_check_scheduler.cc
@@ -1,18 +1,22 @@
-// Copyright (c) 2010 The Chromium OS Authors. All rights reserved.
+// Copyright (c) 2012 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/update_check_scheduler.h"
#include "update_engine/certificate_checker.h"
+#include "update_engine/http_common.h"
#include "update_engine/utils.h"
namespace chromeos_update_engine {
-const int UpdateCheckScheduler::kTimeoutOnce = 7 * 60; // at 7 minutes
-const int UpdateCheckScheduler::kTimeoutPeriodic = 45 * 60; // every 45 minutes
-const int UpdateCheckScheduler::kTimeoutRegularFuzz = 10 * 60; // +/- 5 minutes
-const int UpdateCheckScheduler::kTimeoutMaxBackoff = 4 * 60 * 60; // 4 hours
+// Default update check timeout interval/fuzz values, in seconds. Note that
+// actual fuzz is within +/- half of the indicated value.
+const int UpdateCheckScheduler::kTimeoutInitialInterval = 7 * 60;
+const int UpdateCheckScheduler::kTimeoutPeriodicInterval = 45 * 60;
+const int UpdateCheckScheduler::kTimeoutQuickInterval = 1 * 60;
+const int UpdateCheckScheduler::kTimeoutMaxBackoffInterval = 4 * 60 * 60;
+const int UpdateCheckScheduler::kTimeoutRegularFuzz = 10 * 60;
UpdateCheckScheduler::UpdateCheckScheduler(UpdateAttempter* update_attempter)
: update_attempter_(update_attempter),
@@ -42,9 +46,10 @@
update_attempter_->set_update_check_scheduler(this);
// Kicks off periodic update checks. The first check is scheduled
- // |kTimeoutOnce| seconds from now. Subsequent checks are scheduled by
- // ScheduleNextCheck, normally at |kTimeoutPeriodic|-second intervals.
- ScheduleCheck(kTimeoutOnce, kTimeoutRegularFuzz);
+ // |kTimeoutInitialInterval| seconds from now. Subsequent checks are scheduled
+ // by ScheduleNextCheck, normally at |kTimeoutPeriodicInterval|-second
+ // intervals.
+ ScheduleCheck(kTimeoutInitialInterval, kTimeoutRegularFuzz);
}
bool UpdateCheckScheduler::IsBootDeviceRemovable() {
@@ -75,22 +80,32 @@
}
GTimeoutAddSeconds(interval, StaticCheck);
scheduled_ = true;
- LOG(INFO) << "Next update check in " << interval << " seconds.";
+ LOG(INFO) << "Next update check in " << utils::SecsToHourMinSecStr(interval);
}
gboolean UpdateCheckScheduler::StaticCheck(void* scheduler) {
UpdateCheckScheduler* me = reinterpret_cast<UpdateCheckScheduler*>(scheduler);
CHECK(me->scheduled_);
me->scheduled_ = false;
- if (me->IsOOBEComplete()) {
+
+ static bool is_test_used_once = false;
+ bool is_test = false;
+ if (me->IsOOBEComplete() ||
+ (is_test = !is_test_used_once && GpioHandler::IsGpioSignalingTest())) {
+ if (is_test) {
+ LOG(WARNING)
+ << "test mode signaled, allowing update check prior to OOBE complete";
+ is_test_used_once = true;
+ }
+
// Before updating, we flush any previously generated UMA reports.
CertificateChecker::FlushReport();
- me->update_attempter_->Update("", "", false, false);
+ me->update_attempter_->Update("", "", false, false, is_test);
} else {
// Skips all automatic update checks if the OOBE process is not complete and
// schedules a new check as if it is the first one.
LOG(WARNING) << "Skipping update check because OOBE is not complete.";
- me->ScheduleCheck(kTimeoutOnce, kTimeoutRegularFuzz);
+ me->ScheduleCheck(kTimeoutInitialInterval, kTimeoutRegularFuzz);
}
// This check ensures that future update checks will be or are already
// scheduled. The check should never fail. A check failure means that there's
@@ -102,50 +117,66 @@
return FALSE; // Don't run again.
}
-void UpdateCheckScheduler::ComputeNextIntervalAndFuzz(int* next_interval,
+void UpdateCheckScheduler::ComputeNextIntervalAndFuzz(const int forced_interval,
+ int* next_interval,
int* next_fuzz) {
- int interval = 0;
- if (poll_interval_ > 0) {
- // Server-dictated poll interval.
- interval = poll_interval_;
- LOG(WARNING) << "Using server-dictated poll interval: " << interval;
- } else if (update_attempter_->http_response_code() == 500 ||
- update_attempter_->http_response_code() == 503) {
- // Implements exponential back off on 500 (Internal Server Error) and 503
- // (Service Unavailable) HTTP response codes.
- interval = 2 * last_interval_;
- LOG(WARNING) << "Exponential back off due to 500/503 HTTP response code.";
- }
- if (interval > kTimeoutMaxBackoff) {
- interval = kTimeoutMaxBackoff;
- }
- // Back off and server-dictated poll intervals are fuzzed by +/- |interval|/2.
- int fuzz = interval;
+ CHECK(next_interval && next_fuzz);
- // Ensures that under normal conditions the regular update check interval and
- // fuzz are used. Also covers the case where back off is required based on the
- // initial update check.
- if (interval < kTimeoutPeriodic) {
- interval = kTimeoutPeriodic;
- fuzz = kTimeoutRegularFuzz;
+ int interval = forced_interval;
+ int fuzz = 0; // Use default fuzz value (see below)
+
+ if (interval == 0) {
+ int http_response_code;
+ if (poll_interval_ > 0) {
+ // Server-dictated poll interval.
+ interval = poll_interval_;
+ LOG(WARNING) << "Using server-dictated poll interval: " << interval;
+ } else if ((http_response_code = update_attempter_->http_response_code()) ==
+ kHttpResponseInternalServerError ||
+ http_response_code == kHttpResponseServiceUnavailable) {
+ // Implements exponential back off on 500 (Internal Server Error) and 503
+ // (Service Unavailable) HTTP response codes.
+ interval = 2 * last_interval_;
+ LOG(WARNING) << "Exponential back off due to HTTP response code ("
+ << http_response_code << ")";
+ }
+
+ // Backoff cannot exceed a predetermined maximum period.
+ if (interval > kTimeoutMaxBackoffInterval)
+ interval = kTimeoutMaxBackoffInterval;
+
+ // Ensures that under normal conditions the regular update check interval
+ // and fuzz are used. Also covers the case where back off is required based
+ // on the initial update check.
+ if (interval < kTimeoutPeriodicInterval) {
+ interval = kTimeoutPeriodicInterval;
+ fuzz = kTimeoutRegularFuzz;
+ }
}
+
+ // Set default fuzz to +/- |interval|/2.
+ if (fuzz == 0)
+ fuzz = interval;
+
*next_interval = interval;
*next_fuzz = fuzz;
}
-void UpdateCheckScheduler::ScheduleNextCheck() {
+void UpdateCheckScheduler::ScheduleNextCheck(bool is_force_quick) {
int interval, fuzz;
- ComputeNextIntervalAndFuzz(&interval, &fuzz);
+ ComputeNextIntervalAndFuzz(is_force_quick ? kTimeoutQuickInterval : 0,
+ &interval, &fuzz);
ScheduleCheck(interval, fuzz);
}
-void UpdateCheckScheduler::SetUpdateStatus(UpdateStatus status) {
+void UpdateCheckScheduler::SetUpdateStatus(UpdateStatus status,
+ UpdateNotice notice) {
// We want to schedule the update checks for when we're idle as well as
// after we've successfully applied an update and waiting for the user
// to reboot to ensure our active count is accurate.
if (status == UPDATE_STATUS_IDLE ||
status == UPDATE_STATUS_UPDATED_NEED_REBOOT) {
- ScheduleNextCheck();
+ ScheduleNextCheck(notice == kUpdateNoticeTestAddrFailed);
}
}
diff --git a/update_check_scheduler.h b/update_check_scheduler.h
index 8239c0d..edfbbfd 100644
--- a/update_check_scheduler.h
+++ b/update_check_scheduler.h
@@ -35,10 +35,11 @@
// \---/
class UpdateCheckScheduler {
public:
- static const int kTimeoutOnce;
- static const int kTimeoutPeriodic;
+ static const int kTimeoutInitialInterval;
+ static const int kTimeoutPeriodicInterval;
+ static const int kTimeoutQuickInterval;
static const int kTimeoutRegularFuzz;
- static const int kTimeoutMaxBackoff;
+ static const int kTimeoutMaxBackoffInterval;
UpdateCheckScheduler(UpdateAttempter* update_attempter);
virtual ~UpdateCheckScheduler();
@@ -46,8 +47,10 @@
// Initiates the periodic update checks, if necessary.
void Run();
- // Sets the new update status. This is invoked by UpdateAttempter.
- void SetUpdateStatus(UpdateStatus status);
+ // Sets the new update status. This is invoked by UpdateAttempter. |notice|
+ // is used for passing supplemental information about recent update events,
+ // which may influence scheduling decisions.
+ void SetUpdateStatus(UpdateStatus status, UpdateNotice notice);
void set_poll_interval(int interval) { poll_interval_ = interval; }
int poll_interval() const { return poll_interval_; }
@@ -99,13 +102,16 @@
// attempter.
static gboolean StaticCheck(void* scheduler);
- // Schedules the next update check by setting up a timeout source.
- void ScheduleNextCheck();
+ // Schedules the next update check by setting up a timeout source;
+ // |is_force_quick| will enforce a quick subsequent update check interval.
+ void ScheduleNextCheck(bool is_force_quick);
// Computes the timeout interval along with its random fuzz range for the next
// update check by taking into account the last timeout interval as well as
- // the last update status.
- void ComputeNextIntervalAndFuzz(int* next_interval, int* next_fuzz);
+ // the last update status. A nonzero |forced_interval|, however, will override
+ // all other considerations.
+ void ComputeNextIntervalAndFuzz(const int forced_interval,
+ int* next_interval, int* next_fuzz);
// The UpdateAttempter to use for update checks.
UpdateAttempter* update_attempter_;
diff --git a/update_check_scheduler_unittest.cc b/update_check_scheduler_unittest.cc
index 010cc82..098651b 100644
--- a/update_check_scheduler_unittest.cc
+++ b/update_check_scheduler_unittest.cc
@@ -87,53 +87,57 @@
TEST_F(UpdateCheckSchedulerTest, ComputeNextIntervalAndFuzzBackoffTest) {
int interval, fuzz;
attempter_.set_http_response_code(500);
- int last_interval = UpdateCheckScheduler::kTimeoutPeriodic + 50;
+ int last_interval = UpdateCheckScheduler::kTimeoutPeriodicInterval + 50;
scheduler_.last_interval_ = last_interval;
- scheduler_.ComputeNextIntervalAndFuzz(&interval, &fuzz);
+ scheduler_.ComputeNextIntervalAndFuzz(0, &interval, &fuzz);
EXPECT_EQ(2 * last_interval, interval);
EXPECT_EQ(2 * last_interval, fuzz);
attempter_.set_http_response_code(503);
- scheduler_.last_interval_ = UpdateCheckScheduler::kTimeoutMaxBackoff / 2 + 1;
- scheduler_.ComputeNextIntervalAndFuzz(&interval, &fuzz);
- EXPECT_EQ(UpdateCheckScheduler::kTimeoutMaxBackoff, interval);
- EXPECT_EQ(UpdateCheckScheduler::kTimeoutMaxBackoff, fuzz);
+ scheduler_.last_interval_ =
+ UpdateCheckScheduler::kTimeoutMaxBackoffInterval / 2 + 1;
+ scheduler_.ComputeNextIntervalAndFuzz(0, &interval, &fuzz);
+ EXPECT_EQ(UpdateCheckScheduler::kTimeoutMaxBackoffInterval, interval);
+ EXPECT_EQ(UpdateCheckScheduler::kTimeoutMaxBackoffInterval, fuzz);
}
TEST_F(UpdateCheckSchedulerTest, ComputeNextIntervalAndFuzzPollTest) {
int interval, fuzz;
- int poll_interval = UpdateCheckScheduler::kTimeoutPeriodic + 50;
+ int poll_interval = UpdateCheckScheduler::kTimeoutPeriodicInterval + 50;
scheduler_.set_poll_interval(poll_interval);
- scheduler_.ComputeNextIntervalAndFuzz(&interval, &fuzz);
+ scheduler_.ComputeNextIntervalAndFuzz(0, &interval, &fuzz);
EXPECT_EQ(poll_interval, interval);
EXPECT_EQ(poll_interval, fuzz);
- scheduler_.set_poll_interval(UpdateCheckScheduler::kTimeoutMaxBackoff + 1);
- scheduler_.ComputeNextIntervalAndFuzz(&interval, &fuzz);
- EXPECT_EQ(UpdateCheckScheduler::kTimeoutMaxBackoff, interval);
- EXPECT_EQ(UpdateCheckScheduler::kTimeoutMaxBackoff, fuzz);
+ scheduler_.set_poll_interval(
+ UpdateCheckScheduler::kTimeoutMaxBackoffInterval + 1);
+ scheduler_.ComputeNextIntervalAndFuzz(0, &interval, &fuzz);
+ EXPECT_EQ(UpdateCheckScheduler::kTimeoutMaxBackoffInterval, interval);
+ EXPECT_EQ(UpdateCheckScheduler::kTimeoutMaxBackoffInterval, fuzz);
- scheduler_.set_poll_interval(UpdateCheckScheduler::kTimeoutPeriodic - 1);
- scheduler_.ComputeNextIntervalAndFuzz(&interval, &fuzz);
- EXPECT_EQ(UpdateCheckScheduler::kTimeoutPeriodic, interval);
+ scheduler_.set_poll_interval(
+ UpdateCheckScheduler::kTimeoutPeriodicInterval - 1);
+ scheduler_.ComputeNextIntervalAndFuzz(0, &interval, &fuzz);
+ EXPECT_EQ(UpdateCheckScheduler::kTimeoutPeriodicInterval, interval);
EXPECT_EQ(UpdateCheckScheduler::kTimeoutRegularFuzz, fuzz);
}
TEST_F(UpdateCheckSchedulerTest, ComputeNextIntervalAndFuzzPriorityTest) {
int interval, fuzz;
attempter_.set_http_response_code(500);
- scheduler_.last_interval_ = UpdateCheckScheduler::kTimeoutPeriodic + 50;
- int poll_interval = UpdateCheckScheduler::kTimeoutPeriodic + 100;
+ scheduler_.last_interval_ =
+ UpdateCheckScheduler::kTimeoutPeriodicInterval + 50;
+ int poll_interval = UpdateCheckScheduler::kTimeoutPeriodicInterval + 100;
scheduler_.set_poll_interval(poll_interval);
- scheduler_.ComputeNextIntervalAndFuzz(&interval, &fuzz);
+ scheduler_.ComputeNextIntervalAndFuzz(0, &interval, &fuzz);
EXPECT_EQ(poll_interval, interval);
EXPECT_EQ(poll_interval, fuzz);
}
TEST_F(UpdateCheckSchedulerTest, ComputeNextIntervalAndFuzzTest) {
int interval, fuzz;
- scheduler_.ComputeNextIntervalAndFuzz(&interval, &fuzz);
- EXPECT_EQ(UpdateCheckScheduler::kTimeoutPeriodic, interval);
+ scheduler_.ComputeNextIntervalAndFuzz(0, &interval, &fuzz);
+ EXPECT_EQ(UpdateCheckScheduler::kTimeoutPeriodicInterval, interval);
EXPECT_EQ(UpdateCheckScheduler::kTimeoutRegularFuzz, fuzz);
}
@@ -182,7 +186,7 @@
TEST_F(UpdateCheckSchedulerTest, RunTest) {
int interval_min, interval_max;
- FuzzRange(UpdateCheckScheduler::kTimeoutOnce,
+ FuzzRange(UpdateCheckScheduler::kTimeoutInitialInterval,
UpdateCheckScheduler::kTimeoutRegularFuzz,
&interval_min,
&interval_max);
@@ -227,12 +231,12 @@
TEST_F(UpdateCheckSchedulerTest, ScheduleNextCheckDisabledTest) {
EXPECT_CALL(scheduler_, GTimeoutAddSeconds(_, _)).Times(0);
- scheduler_.ScheduleNextCheck();
+ scheduler_.ScheduleNextCheck(false);
}
TEST_F(UpdateCheckSchedulerTest, ScheduleNextCheckEnabledTest) {
int interval_min, interval_max;
- FuzzRange(UpdateCheckScheduler::kTimeoutPeriodic,
+ FuzzRange(UpdateCheckScheduler::kTimeoutPeriodicInterval,
UpdateCheckScheduler::kTimeoutRegularFuzz,
&interval_min,
&interval_max);
@@ -240,17 +244,17 @@
GTimeoutAddSeconds(AllOf(Ge(interval_min), Le(interval_max)),
scheduler_.StaticCheck)).Times(1);
scheduler_.enabled_ = true;
- scheduler_.ScheduleNextCheck();
+ scheduler_.ScheduleNextCheck(false);
}
TEST_F(UpdateCheckSchedulerTest, SetUpdateStatusIdleDisabledTest) {
EXPECT_CALL(scheduler_, GTimeoutAddSeconds(_, _)).Times(0);
- scheduler_.SetUpdateStatus(UPDATE_STATUS_IDLE);
+ scheduler_.SetUpdateStatus(UPDATE_STATUS_IDLE, kUpdateNoticeUnspecified);
}
TEST_F(UpdateCheckSchedulerTest, SetUpdateStatusIdleEnabledTest) {
int interval_min, interval_max;
- FuzzRange(UpdateCheckScheduler::kTimeoutPeriodic,
+ FuzzRange(UpdateCheckScheduler::kTimeoutPeriodicInterval,
UpdateCheckScheduler::kTimeoutRegularFuzz,
&interval_min,
&interval_max);
@@ -258,20 +262,22 @@
GTimeoutAddSeconds(AllOf(Ge(interval_min), Le(interval_max)),
scheduler_.StaticCheck)).Times(1);
scheduler_.enabled_ = true;
- scheduler_.SetUpdateStatus(UPDATE_STATUS_IDLE);
+ scheduler_.SetUpdateStatus(UPDATE_STATUS_IDLE, kUpdateNoticeUnspecified);
}
TEST_F(UpdateCheckSchedulerTest, SetUpdateStatusNonIdleTest) {
EXPECT_CALL(scheduler_, GTimeoutAddSeconds(_, _)).Times(0);
- scheduler_.SetUpdateStatus(UPDATE_STATUS_DOWNLOADING);
+ scheduler_.SetUpdateStatus(UPDATE_STATUS_DOWNLOADING,
+ kUpdateNoticeUnspecified);
scheduler_.enabled_ = true;
- scheduler_.SetUpdateStatus(UPDATE_STATUS_DOWNLOADING);
+ scheduler_.SetUpdateStatus(UPDATE_STATUS_DOWNLOADING,
+ kUpdateNoticeUnspecified);
}
TEST_F(UpdateCheckSchedulerTest, StaticCheckOOBECompleteTest) {
scheduler_.scheduled_ = true;
EXPECT_CALL(scheduler_, IsOOBEComplete()).Times(1).WillOnce(Return(true));
- EXPECT_CALL(attempter_, Update("", "", false, false))
+ EXPECT_CALL(attempter_, Update("", "", false, false, false))
.Times(1)
.WillOnce(Assign(&scheduler_.scheduled_, true));
scheduler_.enabled_ = true;
@@ -282,9 +288,9 @@
TEST_F(UpdateCheckSchedulerTest, StaticCheckOOBENotCompleteTest) {
scheduler_.scheduled_ = true;
EXPECT_CALL(scheduler_, IsOOBEComplete()).Times(1).WillOnce(Return(false));
- EXPECT_CALL(attempter_, Update("", "", _, _)).Times(0);
+ EXPECT_CALL(attempter_, Update("", "", _, _, _)).Times(0);
int interval_min, interval_max;
- FuzzRange(UpdateCheckScheduler::kTimeoutOnce,
+ FuzzRange(UpdateCheckScheduler::kTimeoutInitialInterval,
UpdateCheckScheduler::kTimeoutRegularFuzz,
&interval_min,
&interval_max);
diff --git a/utils.cc b/utils.cc
index 1c9425a..1c57fbf 100644
--- a/utils.cc
+++ b/utils.cc
@@ -588,6 +588,23 @@
return FALSE;
}
+string SecsToHourMinSecStr(unsigned secs) {
+ // Canonicalize into hours, minutes, seconds.
+ unsigned hours = secs / (60 * 60);
+ secs -= hours * (60 * 60);
+ unsigned mins = secs / 60;
+ secs -= mins * 60;
+
+ // Construct and return string.
+ string str;
+ if (hours)
+ base::StringAppendF(&str, "%uh", hours);
+ if (hours || mins)
+ base::StringAppendF(&str, "%um", mins);
+ base::StringAppendF(&str, "%us", secs);
+ return str;
+}
+
const char* const kStatefulPartition = "/mnt/stateful_partition";
} // namespace utils
diff --git a/utils.h b/utils.h
index 728d992..3c83674 100644
--- a/utils.h
+++ b/utils.h
@@ -251,6 +251,11 @@
// Assumes data points to a Closure. Runs it and returns FALSE;
gboolean GlibRunClosure(gpointer data);
+// Converts seconds into hour-minute-second notation. For example, 185 will
+// yield 3m5s and 4300 will yield 1h11m40s. Zero padding not applied. Seconds
+// are always shown in the result.
+std::string SecsToHourMinSecStr(unsigned secs);
+
} // namespace utils
// Class to unmount FS when object goes out of scope