text: /**
   * 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 "config.h"
  
  #include "hwmonio.hpp"
  
  #include "sysfs.hpp"
  
  #include <algorithm>
  #include <exception>
  #include <fstream>
  #include <thread>
  
  namespace hwmonio
  {
  
  int64_t FileSystem::read(const std::string& path) const
  {
      int64_t val;
      std::ifstream ifs;
      ifs.exceptions(std::ifstream::failbit | std::ifstream::badbit |
                     std::ifstream::eofbit);
  
      errno = 0;
      if (!ifs.is_open())
          ifs.open(path);
      ifs.clear();
      ifs.seekg(0);
      ifs >> val;
  
      return val;
  }
  
  void FileSystem::write(const std::string& path, uint32_t value) const
  {
      std::ofstream ofs;
      ofs.exceptions(std::ofstream::failbit | std::ofstream::badbit |
                     std::ofstream::eofbit);
  
      errno = 0;
      if (!ofs.is_open())
          ofs.open(path);
      ofs.clear();
      ofs.seekp(0);
      ofs << value;
      ofs.flush();
  }
  
  FileSystem fileSystemImpl;
  
  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,
  
      /*
       * Some devices return this if the hardware is being
       * powered off in a normal manner, as incomplete data
       * is received. Retrying allows time for the system to
       * clean up driver devices, or in the event of a real
       * failure, attempt to get the rest of the data.
       */
      EMSGSIZE,
  };
  
  HwmonIO::HwmonIO(const std::string& path, const FileSystemInterface* intf) :
      _p(path), _intf(intf)
  {
  }
  
  int64_t HwmonIO::read(const std::string& type, const std::string& id,
                        const std::string& sensor, size_t retries,
                        std::chrono::milliseconds delay) const
  {
      int64_t val;
      auto fullPath = sysfs::make_sysfs_path(_p, type, id, sensor);
  
      while (true)
      {
          try
          {
              val = _intf->read(fullPath);
          }
          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 (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
  
  {
      auto fullPath = sysfs::make_sysfs_path(_p, type, id, sensor);
  
      // See comments in the read method for an explanation of the odd exception
      // handling behavior here.
  
      while (true)
      {
          try
          {
              _intf->write(fullPath, val);
          }
          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;
  }
  
  } // namespace hwmonio
  