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,