DeviceMgmt: split I2CDeviceParams out of I2CDevice

The parameters struct now exists on its own, separately from the device
struct which instantiates the kernel device in its constructor and
removes it in its destructor.  This allows the parameters to exist
standalone so that we can do things like use an instance of it to check
if a device is already instantiated without attempting to do so.

Signed-off-by: Zev Weiss <zev@bewilderbeest.net>
Change-Id: Ib1c37ee26e27e59020a8e26916e697729a3661af
diff --git a/include/DeviceMgmt.hpp b/include/DeviceMgmt.hpp
index daad381..3eaf897 100644
--- a/include/DeviceMgmt.hpp
+++ b/include/DeviceMgmt.hpp
@@ -15,23 +15,35 @@
 using I2CDeviceTypeMap =
     boost::container::flat_map<std::string, I2CDeviceType, std::less<>>;
 
-struct I2CDevice
+struct I2CDeviceParams
 {
-    I2CDevice(const I2CDeviceType& type, uint64_t bus, uint64_t address) :
+    I2CDeviceParams(const I2CDeviceType& type, uint64_t bus, uint64_t address) :
         type(&type), bus(bus), address(address){};
 
     const I2CDeviceType* type;
     uint64_t bus;
     uint64_t address;
 
-    bool present(void) const;
+    bool devicePresent(void) const;
+};
+
+std::optional<I2CDeviceParams>
+    getI2CDeviceParams(const I2CDeviceTypeMap& dtmap,
+                       const SensorBaseConfigMap& cfg);
+
+class I2CDevice
+{
+  public:
+    explicit I2CDevice(I2CDeviceParams params);
+    ~I2CDevice();
+
+  private:
+    I2CDeviceParams params;
+
     int create(void) const;
     int destroy(void) const;
 };
 
-std::optional<I2CDevice> getI2CDevice(const I2CDeviceTypeMap& dtmap,
-                                      const SensorBaseConfigMap& cfg);
-
 // HACK: this declaration "should" live in Utils.hpp, but that leads to a
 // tangle of header-dependency hell because each header needs types declared
 // in the other.
diff --git a/src/DeviceMgmt.cpp b/src/DeviceMgmt.cpp
index ca52a3a..fdf7577 100644
--- a/src/DeviceMgmt.cpp
+++ b/src/DeviceMgmt.cpp
@@ -5,8 +5,9 @@
 
 namespace fs = std::filesystem;
 
-std::optional<I2CDevice> getI2CDevice(const I2CDeviceTypeMap& dtmap,
-                                      const SensorBaseConfigMap& cfg)
+std::optional<I2CDeviceParams>
+    getI2CDeviceParams(const I2CDeviceTypeMap& dtmap,
+                       const SensorBaseConfigMap& cfg)
 {
     auto findType = cfg.find("Type");
     auto findBus = cfg.find("Bus");
@@ -32,7 +33,7 @@
         return std::nullopt;
     }
 
-    return I2CDevice(findDevType->second, *bus, *addr);
+    return I2CDeviceParams(findDevType->second, *bus, *addr);
 }
 
 static fs::path i2cBusPath(uint64_t bus)
@@ -48,7 +49,7 @@
     return name.str();
 }
 
-bool I2CDevice::present(void) const
+bool I2CDeviceParams::devicePresent(void) const
 {
     fs::path path = i2cBusPath(bus) / deviceDirName(bus, address);
 
@@ -62,16 +63,29 @@
     return fs::exists(path, ec);
 }
 
+I2CDevice::I2CDevice(I2CDeviceParams params) : params(params)
+{
+    if (create() != 0)
+    {
+        throw std::runtime_error("failed to instantiate i2c device");
+    }
+}
+
+I2CDevice::~I2CDevice()
+{
+    destroy();
+}
+
 int I2CDevice::create(void) const
 {
     // If it's already instantiated, there's nothing we need to do.
-    if (present())
+    if (params.devicePresent())
     {
         return 0;
     }
 
     // Try to create it: 'echo $devtype $addr > .../i2c-$bus/new_device'
-    fs::path ctorPath = i2cBusPath(bus) / "new_device";
+    fs::path ctorPath = i2cBusPath(params.bus) / "new_device";
     std::ofstream ctor(ctorPath);
     if (!ctor.good())
     {
@@ -79,7 +93,7 @@
         return -1;
     }
 
-    ctor << type->name << " " << address << "\n";
+    ctor << params.type->name << " " << params.address << "\n";
     ctor.flush();
     if (!ctor.good())
     {
@@ -88,7 +102,7 @@
     }
 
     // Check if that created the requisite sysfs directory
-    if (!present())
+    if (!params.devicePresent())
     {
         destroy();
         return -1;
@@ -99,12 +113,12 @@
 
 int I2CDevice::destroy(void) const
 {
-    // No present() check on this like in create(), since it might be used to
-    // clean up after a device instantiation that was only partially
-    // successful (i.e. when present() would return false but there's still a
-    // dummy i2c client device to remove)
+    // No params.devicePresent() check on this like in create(), since it
+    // might be used to clean up after a device instantiation that was only
+    // partially successful (i.e. when params.devicePresent() would return
+    // false but there's still a dummy i2c client device to remove)
 
-    fs::path dtorPath = i2cBusPath(bus) / "delete_device";
+    fs::path dtorPath = i2cBusPath(params.bus) / "delete_device";
     std::ofstream dtor(dtorPath);
     if (!dtor.good())
     {
@@ -112,7 +126,7 @@
         return -1;
     }
 
-    dtor << address << "\n";
+    dtor << params.address << "\n";
     dtor.flush();
     if (!dtor.good())
     {