fw-update: Implement firmware UpdateManager
The UpdateManager parses the PLDM package and co-ordinates with
the DeviceUpdater to update all the PLDM enabled firmware devices.
Tested: Completed firmware update using PLDM for an FD
Signed-off-by: Tom Joseph <rushtotom@gmail.com>
Change-Id: Ia87675e0a88cb1f72ad82934e539739db193b9f6
diff --git a/fw-update/activation.hpp b/fw-update/activation.hpp
new file mode 100644
index 0000000..ed216d8
--- /dev/null
+++ b/fw-update/activation.hpp
@@ -0,0 +1,150 @@
+#pragma once
+
+#include "fw-update/update_manager.hpp"
+
+#include <sdbusplus/bus.hpp>
+#include <xyz/openbmc_project/Object/Delete/server.hpp>
+#include <xyz/openbmc_project/Software/Activation/server.hpp>
+#include <xyz/openbmc_project/Software/ActivationProgress/server.hpp>
+
+#include <string>
+
+namespace pldm
+{
+
+namespace fw_update
+{
+
+using ActivationIntf = sdbusplus::server::object::object<
+ sdbusplus::xyz::openbmc_project::Software::server::Activation>;
+using ActivationProgressIntf = sdbusplus::server::object::object<
+ sdbusplus::xyz::openbmc_project::Software::server::ActivationProgress>;
+using DeleteIntf = sdbusplus::server::object::object<
+ sdbusplus::xyz::openbmc_project::Object::server::Delete>;
+
+/** @class ActivationProgress
+ *
+ * Concrete implementation of xyz.openbmc_project.Software.ActivationProgress
+ * D-Bus interface
+ */
+class ActivationProgress : public ActivationProgressIntf
+{
+ public:
+ /** @brief Constructor
+ *
+ * @param[in] bus - Bus to attach to
+ * @param[in] objPath - D-Bus object path
+ */
+ ActivationProgress(sdbusplus::bus::bus& bus, const std::string& objPath) :
+ ActivationProgressIntf(bus, objPath.c_str(),
+ action::emit_interface_added)
+ {
+ progress(0);
+ }
+};
+
+/** @class Delete
+ *
+ * Concrete implementation of xyz.openbmc_project.Object.Delete D-Bus interface
+ */
+class Delete : public DeleteIntf
+{
+ public:
+ /** @brief Constructor
+ *
+ * @param[in] bus - Bus to attach to
+ * @param[in] objPath - D-Bus object path
+ * @param[in] updateManager - Reference to FW update manager
+ */
+ Delete(sdbusplus::bus::bus& bus, const std::string& objPath,
+ UpdateManager* updateManager) :
+ DeleteIntf(bus, objPath.c_str(), action::emit_interface_added),
+ updateManager(updateManager)
+ {}
+
+ /** @brief Delete the Activation D-Bus object for the FW update package */
+ void delete_() override
+ {
+ updateManager->clearActivationInfo();
+ }
+
+ private:
+ UpdateManager* updateManager;
+};
+
+/** @class Activation
+ *
+ * Concrete implementation of xyz.openbmc_project.Object.Activation D-Bus
+ * interface
+ */
+class Activation : public ActivationIntf
+{
+ public:
+ /** @brief Constructor
+ *
+ * @param[in] bus - Bus to attach to
+ * @param[in] objPath - D-Bus object path
+ * @param[in] updateManager - Reference to FW update manager
+ */
+ Activation(sdbusplus::bus::bus& bus, std::string objPath,
+ Activations activationStatus, UpdateManager* updateManager) :
+ ActivationIntf(bus, objPath.c_str(), true),
+ bus(bus), objPath(objPath), updateManager(updateManager)
+ {
+ activation(activationStatus);
+ deleteImpl = std::make_unique<Delete>(bus, objPath, updateManager);
+ emit_object_added();
+ }
+
+ using sdbusplus::xyz::openbmc_project::Software::server::Activation::
+ activation;
+ using sdbusplus::xyz::openbmc_project::Software::server::Activation::
+ requestedActivation;
+
+ /** @brief Overriding Activation property setter function
+ */
+ Activations activation(Activations value) override
+ {
+ if (value == Activations::Activating)
+ {
+ deleteImpl.reset();
+ updateManager->activatePackage();
+ }
+ else if (value == Activations::Active || value == Activations::Failed)
+ {
+ if (!deleteImpl)
+ {
+ deleteImpl =
+ std::make_unique<Delete>(bus, objPath, updateManager);
+ }
+ }
+
+ return ActivationIntf::activation(value);
+ }
+
+ /** @brief Overriding RequestedActivations property setter function
+ */
+ RequestedActivations
+ requestedActivation(RequestedActivations value) override
+ {
+ if ((value == RequestedActivations::Active) &&
+ (requestedActivation() != RequestedActivations::Active))
+ {
+ if ((ActivationIntf::activation() == Activations::Ready))
+ {
+ activation(Activations::Activating);
+ }
+ }
+ return ActivationIntf::requestedActivation(value);
+ }
+
+ private:
+ sdbusplus::bus::bus& bus;
+ const std::string objPath;
+ UpdateManager* updateManager;
+ std::unique_ptr<Delete> deleteImpl;
+};
+
+} // namespace fw_update
+
+} // namespace pldm
diff --git a/fw-update/device_updater.cpp b/fw-update/device_updater.cpp
index c8605f4..63e4f73 100644
--- a/fw-update/device_updater.cpp
+++ b/fw-update/device_updater.cpp
@@ -2,6 +2,9 @@
#include "libpldm/firmware_update.h"
+#include "activation.hpp"
+#include "update_manager.hpp"
+
#include <functional>
namespace pldm
@@ -569,6 +572,7 @@
{
std::cout << "Component apply complete, EID=" << unsigned(eid)
<< ", COMPONENT_VERSION=" << compVersion << "\n";
+ updateManager->updateActivationProgress();
}
else
{
@@ -664,6 +668,8 @@
<< "\n";
return;
}
+
+ updateManager->updateDeviceCompletion(eid, true);
}
} // namespace fw_update
diff --git a/fw-update/test/meson.build b/fw-update/test/meson.build
index 2a66c93..757fdc9 100644
--- a/fw-update/test/meson.build
+++ b/fw-update/test/meson.build
@@ -3,6 +3,7 @@
'../inventory_manager.cpp',
'../package_parser.cpp',
'../device_updater.cpp',
+ '../update_manager.cpp',
'../../common/utils.cpp',
'../../pldmd/dbus_impl_requester.cpp',
'../../pldmd/instance_id.cpp'])
diff --git a/fw-update/update_manager.cpp b/fw-update/update_manager.cpp
new file mode 100644
index 0000000..42e2156
--- /dev/null
+++ b/fw-update/update_manager.cpp
@@ -0,0 +1,296 @@
+#include "update_manager.hpp"
+
+#include "activation.hpp"
+#include "common/utils.hpp"
+#include "package_parser.hpp"
+
+#include <cassert>
+#include <cmath>
+#include <filesystem>
+#include <fstream>
+#include <string>
+
+namespace pldm
+{
+
+namespace fw_update
+{
+
+namespace fs = std::filesystem;
+namespace software = sdbusplus::xyz::openbmc_project::Software::server;
+
+int UpdateManager::processPackage(const std::filesystem::path& packageFilePath)
+{
+ // If no devices discovered, take no action on the package.
+ if (!descriptorMap.size())
+ {
+ return 0;
+ }
+
+ namespace software = sdbusplus::xyz::openbmc_project::Software::server;
+ // If a firmware activation of a package is in progress, don't proceed with
+ // package processing
+ if (activation)
+ {
+
+ if (activation->activation() ==
+ software::Activation::Activations::Activating)
+ {
+ std::cerr
+ << "Activation of PLDM FW update package already in progress"
+ << ", PACKAGE_VERSION=" << parser->pkgVersion << "\n";
+ std::filesystem::remove(packageFilePath);
+ return -1;
+ }
+ else
+ {
+ clearActivationInfo();
+ }
+ }
+
+ package.open(packageFilePath,
+ std::ios::binary | std::ios::in | std::ios::ate);
+ if (!package.good())
+ {
+ std::cerr << "Opening the PLDM FW update package failed, ERR="
+ << unsigned(errno) << ", PACKAGEFILE=" << packageFilePath
+ << "\n";
+ package.close();
+ std::filesystem::remove(packageFilePath);
+ return -1;
+ }
+
+ uintmax_t packageSize = package.tellg();
+ if (packageSize < sizeof(pldm_package_header_information))
+ {
+ std::cerr << "PLDM FW update package length less than the length of "
+ "the package header information, PACKAGESIZE="
+ << packageSize << "\n";
+ package.close();
+ std::filesystem::remove(packageFilePath);
+ return -1;
+ }
+
+ package.seekg(0);
+ std::vector<uint8_t> packageHeader(sizeof(pldm_package_header_information));
+ package.read(reinterpret_cast<char*>(packageHeader.data()),
+ sizeof(pldm_package_header_information));
+
+ auto pkgHeaderInfo =
+ reinterpret_cast<const pldm_package_header_information*>(
+ packageHeader.data());
+ auto pkgHeaderInfoSize = sizeof(pldm_package_header_information) +
+ pkgHeaderInfo->package_version_string_length;
+ packageHeader.clear();
+ packageHeader.resize(pkgHeaderInfoSize);
+ package.seekg(0);
+ package.read(reinterpret_cast<char*>(packageHeader.data()),
+ pkgHeaderInfoSize);
+
+ parser = parsePkgHeader(packageHeader);
+ if (parser == nullptr)
+ {
+ std::cerr << "Invalid PLDM package header information"
+ << "\n";
+ package.close();
+ std::filesystem::remove(packageFilePath);
+ return -1;
+ }
+
+ // Populate object path with the hash of the package version
+ size_t versionHash = std::hash<std::string>{}(parser->pkgVersion);
+ objPath = swRootPath + std::to_string(versionHash);
+
+ package.seekg(0);
+ packageHeader.resize(parser->pkgHeaderSize);
+ package.read(reinterpret_cast<char*>(packageHeader.data()),
+ parser->pkgHeaderSize);
+ try
+ {
+ parser->parse(packageHeader, packageSize);
+ }
+ catch (const std::exception& e)
+ {
+ std::cerr << "Invalid PLDM package header"
+ << "\n";
+ activation = std::make_unique<Activation>(
+ pldm::utils::DBusHandler::getBus(), objPath,
+ software::Activation::Activations::Invalid, this);
+ package.close();
+ parser.reset();
+ return -1;
+ }
+
+ auto deviceUpdaterInfos =
+ associatePkgToDevices(parser->getFwDeviceIDRecords(), descriptorMap,
+ totalNumComponentUpdates);
+ if (!deviceUpdaterInfos.size())
+ {
+ std::cerr
+ << "No matching devices found with the PLDM firmware update package"
+ << "\n";
+ activation = std::make_unique<Activation>(
+ pldm::utils::DBusHandler::getBus(), objPath,
+ software::Activation::Activations::Invalid, this);
+ package.close();
+ parser.reset();
+ return 0;
+ }
+
+ const auto& fwDeviceIDRecords = parser->getFwDeviceIDRecords();
+ const auto& compImageInfos = parser->getComponentImageInfos();
+
+ for (const auto& deviceUpdaterInfo : deviceUpdaterInfos)
+ {
+ const auto& fwDeviceIDRecord =
+ fwDeviceIDRecords[deviceUpdaterInfo.second];
+ auto search = componentInfoMap.find(deviceUpdaterInfo.first);
+ deviceUpdaterMap.emplace(deviceUpdaterInfo.first,
+ std::make_unique<DeviceUpdater>(
+ deviceUpdaterInfo.first, event, requester,
+ handler, package, fwDeviceIDRecord,
+ compImageInfos, search->second,
+ MAXIMUM_TRANSFER_SIZE, this));
+ }
+
+ fwPackageFilePath = packageFilePath;
+ activation = std::make_unique<Activation>(
+ pldm::utils::DBusHandler::getBus(), objPath,
+ software::Activation::Activations::Ready, this);
+ activationProgress = std::make_unique<ActivationProgress>(
+ pldm::utils::DBusHandler::getBus(), objPath);
+
+ return 0;
+}
+
+DeviceUpdaterInfos UpdateManager::associatePkgToDevices(
+ const FirmwareDeviceIDRecords& fwDeviceIDRecords,
+ const DescriptorMap& descriptorMap,
+ TotalComponentUpdates& totalNumComponentUpdates)
+{
+ DeviceUpdaterInfos deviceUpdaterInfos;
+ for (size_t index = 0; index < fwDeviceIDRecords.size(); ++index)
+ {
+ const auto& deviceIDDescriptors =
+ std::get<Descriptors>(fwDeviceIDRecords[index]);
+ for (const auto& [eid, descriptors] : descriptorMap)
+ {
+ if (std::includes(descriptors.begin(), descriptors.end(),
+ deviceIDDescriptors.begin(),
+ deviceIDDescriptors.end()))
+ {
+ deviceUpdaterInfos.emplace_back(std::make_pair(eid, index));
+ const auto& applicableComponents =
+ std::get<ApplicableComponents>(fwDeviceIDRecords[index]);
+ totalNumComponentUpdates += applicableComponents.size();
+ }
+ }
+ }
+ return deviceUpdaterInfos;
+}
+
+void UpdateManager::updateDeviceCompletion(mctp_eid_t eid, bool status)
+{
+ deviceUpdateCompletionMap.emplace(eid, status);
+ if (deviceUpdateCompletionMap.size() == deviceUpdaterMap.size())
+ {
+ for (const auto& [eid, status] : deviceUpdateCompletionMap)
+ {
+ if (!status)
+ {
+ activation->activation(
+ software::Activation::Activations::Failed);
+ return;
+ }
+ }
+
+ auto endTime = std::chrono::steady_clock::now();
+ std::cerr << "Firmware update time: "
+ << std::chrono::duration<double, std::milli>(endTime -
+ startTime)
+ .count()
+ << " ms\n";
+ activation->activation(software::Activation::Activations::Active);
+ }
+ return;
+}
+
+Response UpdateManager::handleRequest(mctp_eid_t eid, uint8_t command,
+ const pldm_msg* request, size_t reqMsgLen)
+{
+ Response response(sizeof(pldm_msg), 0);
+ if (deviceUpdaterMap.contains(eid))
+ {
+ auto search = deviceUpdaterMap.find(eid);
+ if (command == PLDM_REQUEST_FIRMWARE_DATA)
+ {
+ return search->second->requestFwData(request, reqMsgLen);
+ }
+ else if (command == PLDM_TRANSFER_COMPLETE)
+ {
+ return search->second->transferComplete(request, reqMsgLen);
+ }
+ else if (command == PLDM_VERIFY_COMPLETE)
+ {
+ return search->second->verifyComplete(request, reqMsgLen);
+ }
+ else if (command == PLDM_APPLY_COMPLETE)
+ {
+ return search->second->applyComplete(request, reqMsgLen);
+ }
+ else
+ {
+ auto ptr = reinterpret_cast<pldm_msg*>(response.data());
+ auto rc = encode_cc_only_resp(
+ request->hdr.instance_id, request->hdr.type,
+ request->hdr.command, PLDM_ERROR_INVALID_DATA, ptr);
+ assert(rc == PLDM_SUCCESS);
+ }
+ }
+ else
+ {
+ auto ptr = reinterpret_cast<pldm_msg*>(response.data());
+ auto rc = encode_cc_only_resp(request->hdr.instance_id,
+ request->hdr.type, +request->hdr.command,
+ PLDM_FWUP_COMMAND_NOT_EXPECTED, ptr);
+ assert(rc == PLDM_SUCCESS);
+ }
+
+ return response;
+}
+
+void UpdateManager::activatePackage()
+{
+ startTime = std::chrono::steady_clock::now();
+ for (const auto& [eid, deviceUpdaterPtr] : deviceUpdaterMap)
+ {
+ deviceUpdaterPtr->startFwUpdateFlow();
+ }
+}
+
+void UpdateManager::clearActivationInfo()
+{
+ activation.reset();
+ activationProgress.reset();
+ objPath.clear();
+
+ deviceUpdaterMap.clear();
+ deviceUpdateCompletionMap.clear();
+ parser.reset();
+ package.close();
+ std::filesystem::remove(fwPackageFilePath);
+ totalNumComponentUpdates = 0;
+ compUpdateCompletedCount = 0;
+}
+
+void UpdateManager::updateActivationProgress()
+{
+ compUpdateCompletedCount++;
+ auto progressPercent = static_cast<uint8_t>(std::floor(
+ (100 * compUpdateCompletedCount) / totalNumComponentUpdates));
+ activationProgress->progress(progressPercent);
+}
+
+} // namespace fw_update
+
+} // namespace pldm
\ No newline at end of file
diff --git a/fw-update/update_manager.hpp b/fw-update/update_manager.hpp
new file mode 100644
index 0000000..c942e38
--- /dev/null
+++ b/fw-update/update_manager.hpp
@@ -0,0 +1,136 @@
+#pragma once
+
+#include "libpldm/base.h"
+#include "libpldm/requester/pldm.h"
+
+#include "common/types.hpp"
+#include "device_updater.hpp"
+#include "package_parser.hpp"
+#include "pldmd/dbus_impl_requester.hpp"
+#include "requester/handler.hpp"
+#include "watch.hpp"
+
+#include <chrono>
+#include <filesystem>
+#include <fstream>
+#include <tuple>
+#include <unordered_map>
+
+namespace pldm
+{
+
+namespace fw_update
+{
+
+using namespace sdeventplus;
+using namespace sdeventplus::source;
+using namespace pldm::dbus_api;
+using namespace pldm;
+
+using DeviceIDRecordOffset = size_t;
+using DeviceUpdaterInfo = std::pair<mctp_eid_t, DeviceIDRecordOffset>;
+using DeviceUpdaterInfos = std::vector<DeviceUpdaterInfo>;
+using TotalComponentUpdates = size_t;
+
+class Activation;
+class ActivationProgress;
+
+class UpdateManager
+{
+ public:
+ UpdateManager() = delete;
+ UpdateManager(const UpdateManager&) = delete;
+ UpdateManager(UpdateManager&&) = delete;
+ UpdateManager& operator=(const UpdateManager&) = delete;
+ UpdateManager& operator=(UpdateManager&&) = delete;
+ ~UpdateManager() = default;
+
+ explicit UpdateManager(
+ Event& event,
+ pldm::requester::Handler<pldm::requester::Request>& handler,
+ Requester& requester, const DescriptorMap& descriptorMap,
+ const ComponentInfoMap& componentInfoMap) :
+ event(event),
+ handler(handler), requester(requester), descriptorMap(descriptorMap),
+ componentInfoMap(componentInfoMap),
+ watch(event.get(),
+ std::bind_front(&UpdateManager::processPackage, this))
+ {}
+
+ /** @brief Handle PLDM request for the commands in the FW update
+ * specification
+ *
+ * @param[in] eid - Remote MCTP Endpoint ID
+ * @param[in] command - PLDM command code
+ * @param[in] request - PLDM request message
+ * @param[in] requestLen - PLDM request message length
+ *
+ * @return PLDM response message
+ */
+ Response handleRequest(mctp_eid_t eid, uint8_t command,
+ const pldm_msg* request, size_t reqMsgLen);
+
+ int processPackage(const std::filesystem::path& packageFilePath);
+
+ void updateDeviceCompletion(mctp_eid_t eid, bool status);
+
+ void updateActivationProgress();
+
+ /** @brief Callback function that will be invoked when the
+ * RequestedActivation will be set to active in the Activation
+ * interface
+ */
+ void activatePackage();
+
+ void clearActivationInfo();
+
+ /** @brief
+ *
+ */
+ DeviceUpdaterInfos
+ associatePkgToDevices(const FirmwareDeviceIDRecords& fwDeviceIDRecords,
+ const DescriptorMap& descriptorMap,
+ TotalComponentUpdates& totalNumComponentUpdates);
+
+ const std::string swRootPath{"/xyz/openbmc_project/software/"};
+
+ private:
+ Event& event; //!< reference to PLDM daemon's main event loop
+ /** @brief PLDM request handler */
+ pldm::requester::Handler<pldm::requester::Request>& handler;
+ Requester& requester; //!< reference to Requester object
+ /** @brief Device identifiers of the managed FDs */
+ const DescriptorMap& descriptorMap;
+ /** @brief Component information needed for the update of the managed FDs */
+ const ComponentInfoMap& componentInfoMap;
+ Watch watch;
+
+ std::unique_ptr<Activation> activation;
+ std::unique_ptr<ActivationProgress> activationProgress;
+ std::string objPath;
+
+ std::filesystem::path fwPackageFilePath;
+ std::unique_ptr<PackageParser> parser;
+ std::ifstream package;
+
+ std::unordered_map<mctp_eid_t, std::unique_ptr<DeviceUpdater>>
+ deviceUpdaterMap;
+ std::unordered_map<mctp_eid_t, bool> deviceUpdateCompletionMap;
+
+ /** @brief Total number of component updates to calculate the progress of
+ * the Firmware activation
+ */
+ size_t totalNumComponentUpdates;
+
+ /** @brief FW update package can contain updates for multiple firmware
+ * devices and each device can have multiple components. Once
+ * each component is updated (Transfer completed, Verified and
+ * Applied) ActivationProgress is updated.
+ */
+ size_t compUpdateCompletedCount;
+ decltype(std::chrono::steady_clock::now()) startTime;
+};
+
+} // namespace fw_update
+
+} // namespace pldm
\ No newline at end of file
diff --git a/fw-update/watch.cpp b/fw-update/watch.cpp
new file mode 100644
index 0000000..208d818
--- /dev/null
+++ b/fw-update/watch.cpp
@@ -0,0 +1,111 @@
+#include "watch.hpp"
+
+#include <sys/inotify.h>
+#include <unistd.h>
+
+#include <cstddef>
+#include <cstring>
+#include <filesystem>
+#include <stdexcept>
+#include <string>
+
+namespace pldm
+{
+
+namespace fw_update
+{
+
+// using namespace phosphor::logging;
+using namespace std::string_literals;
+namespace fs = std::filesystem;
+
+Watch::Watch(sd_event* loop, std::function<int(std::string&)> imageCallback) :
+ imageCallback(imageCallback)
+{
+ // Check if IMAGE DIR exists.
+ fs::path imgDirPath("/tmp/images");
+ if (!fs::is_directory(imgDirPath))
+ {
+ fs::create_directories(imgDirPath);
+ }
+
+ fd = inotify_init1(IN_NONBLOCK);
+ if (-1 == fd)
+ {
+ // Store a copy of errno, because the string creation below will
+ // invalidate errno due to one more system calls.
+ auto error = errno;
+ throw std::runtime_error("inotify_init1 failed, errno="s +
+ std::strerror(error));
+ }
+
+ wd = inotify_add_watch(fd, "/tmp/images", IN_CLOSE_WRITE);
+ if (-1 == wd)
+ {
+ auto error = errno;
+ close(fd);
+ throw std::runtime_error("inotify_add_watch failed, errno="s +
+ std::strerror(error));
+ }
+
+ auto rc = sd_event_add_io(loop, nullptr, fd, EPOLLIN, callback, this);
+ if (0 > rc)
+ {
+ throw std::runtime_error("failed to add to event loop, rc="s +
+ std::strerror(-rc));
+ }
+}
+
+Watch::~Watch()
+{
+ if (-1 != fd)
+ {
+ if (-1 != wd)
+ {
+ inotify_rm_watch(fd, wd);
+ }
+ close(fd);
+ }
+}
+
+int Watch::callback(sd_event_source* /* s */, int fd, uint32_t revents,
+ void* userdata)
+{
+ if (!(revents & EPOLLIN))
+ {
+ return 0;
+ }
+
+ constexpr auto maxBytes = 1024;
+ uint8_t buffer[maxBytes];
+ auto bytes = read(fd, buffer, maxBytes);
+ if (0 > bytes)
+ {
+ auto error = errno;
+ throw std::runtime_error("failed to read inotify event, errno="s +
+ std::strerror(error));
+ }
+
+ auto offset = 0;
+ while (offset < bytes)
+ {
+ auto event = reinterpret_cast<inotify_event*>(&buffer[offset]);
+ if ((event->mask & IN_CLOSE_WRITE) && !(event->mask & IN_ISDIR))
+ {
+ auto tarballPath = std::string{"/tmp/images"} + '/' + event->name;
+ auto rc = static_cast<Watch*>(userdata)->imageCallback(tarballPath);
+ if (rc < 0)
+ {
+ // log<level::ERR>("Error processing image",
+ // entry("IMAGE=%s", tarballPath.c_str()));
+ }
+ }
+
+ offset += offsetof(inotify_event, name) + event->len;
+ }
+
+ return 0;
+}
+
+} // namespace fw_update
+} // namespace pldm
diff --git a/fw-update/watch.hpp b/fw-update/watch.hpp
new file mode 100644
index 0000000..3fbf342
--- /dev/null
+++ b/fw-update/watch.hpp
@@ -0,0 +1,64 @@
+#pragma once
+
+#include <systemd/sd-event.h>
+
+#include <functional>
+#include <string>
+
+namespace pldm
+{
+
+namespace fw_update
+{
+
+/** @class Watch
+ *
+ * @brief Adds inotify watch on software image upload directory
+ *
+ * The inotify watch is hooked up with sd-event, so that on call back,
+ * appropriate actions related to a software image upload can be taken.
+ */
+class Watch
+{
+ public:
+ /** @brief ctor - hook inotify watch with sd-event
+ *
+ * @param[in] loop - sd-event object
+ * @param[in] imageCallback - The callback function for processing
+ * the image
+ */
+ Watch(sd_event* loop, std::function<int(std::string&)> imageCallback);
+
+ Watch(const Watch&) = delete;
+ Watch& operator=(const Watch&) = delete;
+ Watch(Watch&&) = delete;
+ Watch& operator=(Watch&&) = delete;
+
+ /** @brief dtor - remove inotify watch and close fd's
+ */
+ ~Watch();
+
+ private:
+ /** @brief sd-event callback
+ *
+ * @param[in] s - event source, floating (unused) in our case
+ * @param[in] fd - inotify fd
+ * @param[in] revents - events that matched for fd
+ * @param[in] userdata - pointer to Watch object
+ * @returns 0 on success, -1 on fail
+ */
+ static int callback(sd_event_source* s, int fd, uint32_t revents,
+ void* userdata);
+
+ /** @brief image upload directory watch descriptor */
+ int wd = -1;
+
+ /** @brief inotify file descriptor */
+ int fd = -1;
+
+ /** @brief The callback function for processing the image. */
+ std::function<int(std::string&)> imageCallback;
+};
+
+} // namespace fw_update
+} // namespace pldm
diff --git a/meson.build b/meson.build
index a8ed7e5..03e2572 100644
--- a/meson.build
+++ b/meson.build
@@ -198,6 +198,8 @@
'fw-update/inventory_manager.cpp',
'fw-update/package_parser.cpp',
'fw-update/device_updater.cpp',
+ 'fw-update/watch.cpp',
+ 'fw-update/update_manager.cpp',
implicit_include_directories: false,
dependencies: deps,
install: true,