Add hwmon io wrapper class

Add a convenience class for doing hwmon attribute sysfs
IO.

A convenience class is warranted given some necessary
workarounds for some GCC io exception bugs, and more importantly
to remove the burden of the rest of the application
checking for ENOENT in the case of a driver unbind event.

Change-Id: I73d5a9aaaac1d5546109ae18854fe5db0b5acb26
Signed-off-by: Brad Bishop <bradleyb@fuzziesquirrel.com>
diff --git a/sysfs.cpp b/sysfs.cpp
index 6d52029..cf1b384 100644
--- a/sysfs.cpp
+++ b/sysfs.cpp
@@ -13,6 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+#include <cerrno>
 #include <cstdlib>
 #include <experimental/filesystem>
 #include <fstream>
@@ -311,5 +312,108 @@
     return value;
 }
 
+namespace hwmonio
+{
+
+HwmonIO::HwmonIO(const std::string& path) : p(path)
+{
+
+}
+
+uint32_t HwmonIO::read(
+        const std::string& type,
+        const std::string& id,
+        const std::string& sensor) const
+{
+    uint32_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);
+    try
+    {
+        ifs.open(fullPath);
+        ifs >> val;
+    }
+    catch (const std::exception& e)
+    {
+        auto rc = errno;
+
+        if (rc == ENOENT)
+        {
+            // If the directory 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 (rc)
+        {
+            // Work around GCC bugs 53984 and 66145 for callers by
+            // explicitly raising system_error here.
+            throw std::system_error(rc, std::generic_category());
+        }
+
+        throw;
+    }
+    return val;
+}
+
+void HwmonIO::write(
+        uint32_t val,
+        const std::string& type,
+        const std::string& id,
+        const std::string& sensor) 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.
+
+    try
+    {
+        ofs.open(fullPath);
+        ofs << val;
+    }
+    catch (const std::exception& e)
+    {
+        auto rc = errno;
+
+        if (rc == ENOENT)
+        {
+            exit(0);
+        }
+
+        if (rc)
+        {
+            throw std::system_error(rc, std::generic_category());
+        }
+
+        throw;
+    }
+}
+
+std::string HwmonIO::path() const
+{
+    return p;
+}
+
+} // namespace hwmonio
 }
 // vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4
diff --git a/sysfs.hpp b/sysfs.hpp
index f0e92db..a526198 100644
--- a/sysfs.hpp
+++ b/sysfs.hpp
@@ -116,6 +116,81 @@
                                const std::string& id,
                                const std::string& sensor);
 
+namespace hwmonio
+{
+
+/** @class HwmonIO
+ *  @brief Convenience wrappers for HWMON sysfs attribute IO.
+ *
+ *  Unburden the rest of the application from having to check
+ *  ENOENT after every hwmon attribute io operation.  Hwmon
+ *  device drivers can be unbound at any time; the program
+ *  cannot always be terminated externally before we try to
+ *  do an io.
+ */
+class HwmonIO
+{
+    public:
+        HwmonIO() = delete;
+        HwmonIO(const HwmonIO&) = default;
+        HwmonIO(HwmonIO&&) = default;
+        HwmonIO& operator=(const HwmonIO&) = default;
+        HwmonIO& operator=(HwmonIO&&) = default;
+        ~HwmonIO() = default;
+
+        /** @brief Constructor
+         *
+         *  @param[in] path - hwmon instance root - eg:
+         *      /sys/class/hwmon/hwmon<N>
+         */
+        explicit HwmonIO(const std::string& path);
+
+        /** @brief Perform formatted hwmon sysfs read.
+         *
+         *  Propogates any exceptions other than ENOENT.
+         *  ENOENT will result in a call to exit(0) in case
+         *  the underlying hwmon driver is unbound and
+         *  the program is inadvertently left running.
+         *
+         *  @param[in] type - The hwmon type (ex. temp).
+         *  @param[in] id - The hwmon id (ex. 1).
+         *  @param[in] sensor - The hwmon sensor (ex. input).
+         *
+         *  @return val - The read value.
+         */
+        uint32_t read(
+                const std::string& type,
+                const std::string& id,
+                const std::string& sensor) const;
+
+        /** @brief Perform formatted hwmon sysfs write.
+         *
+         *  Propogates any exceptions other than ENOENT.
+         *  ENOENT will result in a call to exit(0) in case
+         *  the underlying hwmon driver is unbound and
+         *  the program is inadvertently left running.
+         *
+         *  @param[in] val - The value to be written.
+         *  @param[in] type - The hwmon type (ex. fan).
+         *  @param[in] id - The hwmon id (ex. 1).
+         *  @param[in] sensor - The hwmon sensor (ex. target).
+         */
+        void write(
+                uint32_t val,
+                const std::string& type,
+                const std::string& id,
+                const std::string& sensor) const;
+
+        /** @brief Hwmon instance path access.
+         *
+         *  @return path - The hwmon instance path.
+         */
+        std::string path() const;
+
+    private:
+        std::string p;
+};
+} // namespace hwmonio
 }
 
 // vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4