hwmonio: split IoAccess object out

Split out the hwmon io access object from the sysfs namespace
into its own.

Change-Id: I8d1a45630117d1d503d0d5fa6061163911b695e8
Signed-off-by: Patrick Venture <venture@google.com>
diff --git a/hwmonio.cpp b/hwmonio.cpp
new file mode 100644
index 0000000..fe167f5
--- /dev/null
+++ b/hwmonio.cpp
@@ -0,0 +1,228 @@
+/**
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include <algorithm>
+#include <exception>
+#include <fstream>
+#include <thread>
+
+#include "config.h"
+#include "hwmonio.hpp"
+#include "sysfs.hpp"
+
+namespace hwmonio {
+
+static constexpr auto retryableErrors = {
+    /*
+     * Retry on bus or device errors or timeouts in case
+     * they are transient.
+     */
+    EIO,
+    ETIMEDOUT,
+
+    /*
+     * Retry CRC errors.
+     */
+    EBADMSG,
+
+    /*
+     * Some hwmon drivers do this when they aren't ready
+     * instead of blocking.  Retry.
+     */
+    EAGAIN,
+    /*
+     * We'll see this when for example i2c devices are
+     * unplugged but the driver is still bound.  Retry
+     * rather than exit on the off chance the device is
+     * plugged back in and the driver doesn't do a
+     * remove/probe.  If a remove does occur, we'll
+     * eventually get ENOENT.
+     */
+    ENXIO,
+
+    /*
+     * Some devices return this when they are busy doing
+     * something else.  Even if being busy isn't the cause,
+     * a retry still gives this app a shot at getting data
+     * as opposed to failing out on the first try.
+     */
+    ENODATA,
+};
+
+HwmonIO::HwmonIO(const std::string& path) : p(path)
+{
+}
+
+int64_t HwmonIO::read(
+        const std::string& type,
+        const std::string& id,
+        const std::string& sensor,
+        size_t retries,
+        std::chrono::milliseconds delay,
+        bool isOCC) const
+{
+    int64_t val;
+    std::ifstream ifs;
+    auto fullPath = sysfs::make_sysfs_path(
+            p, type, id, sensor);
+
+    ifs.exceptions(
+            std::ifstream::failbit |
+                std::ifstream::badbit |
+                std::ifstream::eofbit);
+
+    while (true)
+    {
+        try
+        {
+            errno = 0;
+            if (!ifs.is_open())
+                ifs.open(fullPath);
+            ifs.clear();
+            ifs.seekg(0);
+            ifs >> val;
+        }
+        catch (const std::exception& e)
+        {
+            auto rc = errno;
+
+            if (!rc)
+            {
+                throw;
+            }
+
+            if (rc == ENOENT || rc == ENODEV)
+            {
+                // If the directory or device disappeared then this application
+                // should gracefully exit.  There are race conditions between the
+                // unloading of a hwmon driver and the stopping of this service
+                // by systemd.  To prevent this application from falsely failing
+                // in these scenarios, it will simply exit if the directory or
+                // file can not be found.  It is up to the user(s) of this
+                // provided hwmon object to log the appropriate errors if the
+                // object disappears when it should not.
+                exit(0);
+            }
+
+            if (isOCC)
+            {
+                if (rc == EREMOTEIO)
+                {
+                    // For the OCCs, when an EREMOTEIO is return, set the
+                    // value to 255*1000
+                    // (0xFF = sensor is failed, 1000 = sensor factor)
+                    val = 255000;
+                    break;
+                }
+            }
+
+            if (0 == std::count(
+                        retryableErrors.begin(),
+                        retryableErrors.end(),
+                        rc) ||
+                    !retries)
+            {
+                // Not a retryable error or out of retries.
+#ifdef NEGATIVE_ERRNO_ON_FAIL
+                return -rc;
+#endif
+
+                // Work around GCC bugs 53984 and 66145 for callers by
+                // explicitly raising system_error here.
+                throw std::system_error(rc, std::generic_category());
+            }
+
+            --retries;
+            std::this_thread::sleep_for(delay);
+            continue;
+        }
+        break;
+    }
+
+    return val;
+}
+
+void HwmonIO::write(
+        uint32_t val,
+        const std::string& type,
+        const std::string& id,
+        const std::string& sensor,
+        size_t retries,
+        std::chrono::milliseconds delay) const
+
+{
+    std::ofstream ofs;
+    auto fullPath = sysfs::make_sysfs_path(
+            p, type, id, sensor);
+
+    ofs.exceptions(
+            std::ofstream::failbit |
+                std::ofstream::badbit |
+                std::ofstream::eofbit);
+
+    // See comments in the read method for an explanation of the odd exception
+    // handling behavior here.
+
+    while (true)
+    {
+        try
+        {
+            errno = 0;
+            if (!ofs.is_open())
+                ofs.open(fullPath);
+            ofs.clear();
+            ofs.seekp(0);
+            ofs << val;
+            ofs.flush();
+        }
+        catch (const std::exception& e)
+        {
+            auto rc = errno;
+
+            if (!rc)
+            {
+                throw;
+            }
+
+            if (rc == ENOENT)
+            {
+                exit(0);
+            }
+
+            if (0 == std::count(
+                        retryableErrors.begin(),
+                        retryableErrors.end(),
+                        rc) ||
+                    !retries)
+            {
+                // Not a retryable error or out of retries.
+
+                // Work around GCC bugs 53984 and 66145 for callers by
+                // explicitly raising system_error here.
+                throw std::system_error(rc, std::generic_category());
+            }
+
+            --retries;
+            std::this_thread::sleep_for(delay);
+            continue;
+        }
+        break;
+    }
+}
+
+std::string HwmonIO::path() const
+{
+    return p;
+}
+
+} // hwmonio