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/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