Implement cable monitor
Cable monitor consumes a cable configuration file which mentions which
cables are expected to be connected. This configuration file would be
staged by an external service running in the DC. Cable monitor also
consumes the Cable inventory from EM and compares that against the
expected cables from configuration file. Based on this, it generates
cable connected/disconnected events. For more information, refer to
design https://gerrit.openbmc.org/c/openbmc/docs/+/74799.
Related PRs:
https://gerrit.openbmc.org/c/openbmc/phosphor-dbus-interfaces/+/74397
Tested:
Testing has been performed with simulated configuration.
```
>> busctl tree xyz.openbmc_project.EntityManager
`- /xyz
`- /xyz/openbmc_project
|- /xyz/openbmc_project/EntityManager
`- /xyz/openbmc_project/inventory
`- /xyz/openbmc_project/inventory/system
`- /xyz/openbmc_project/inventory/system/cable
|- /xyz/openbmc_project/inventory/system/cable/Yosemite_4_Tray_Cable
| |- /xyz/openbmc_project/inventory/system/cable/Yosemite_4_Tray_Cable/TrayDetector1
| `- /xyz/openbmc_project/inventory/system/cable/Yosemite_4_Tray_Cable/TrayDetector2
`- /xyz/openbmc_project/inventory/system/cable/Yosemite_4_Tray_Cable_2
|- /xyz/openbmc_project/inventory/system/cable/Yosemite_4_Tray_Cable_2/TrayDetector3
`- /xyz/openbmc_project/inventory/system/cable/Yosemite_4_Tray_Cable_2/TrayDetector4
>> cat /var/lib/cablemonitor.d/cable-config.json
{
"ConnectedCables": ["Yosemite 4 Tray Cable", "Yosemite 4 Tray Cable 2"]
}
>> busctl tree xyz.openbmc_project.EntityManager
`- /xyz
`- /xyz/openbmc_project
|- /xyz/openbmc_project/EntityManager
`- /xyz/openbmc_project/inventory
`- /xyz/openbmc_project/inventory/system
`- /xyz/openbmc_project/inventory/system/cable
`- /xyz/openbmc_project/inventory/system/cable/Yosemite_4_Tray_Cable
|- /xyz/openbmc_project/inventory/system/cable/Yosemite_4_Tray_Cable/TrayDetector1
`- /xyz/openbmc_project/inventory/system/cable/Yosemite_4_Tray_Cable/TrayDetector2
>> curl -k -H "X-Auth-Token: $token" -X GET https://${bmc}/redfish/v1/Systems/system/LogServices/EventLog/Entries
{
"@odata.id": "/redfish/v1/Systems/system/LogServices/EventLog/Entries",
"@odata.type": "#LogEntryCollection.LogEntryCollection",
"Description": "Collection of System Event Log Entries",
"Members": [
...
{
"@odata.id": "/redfish/v1/Systems/system/LogServices/EventLog/Entries/3",
"@odata.type": "#LogEntry.v1_9_0.LogEntry",
"AdditionalDataURI": "/redfish/v1/Systems/system/LogServices/EventLog/Entries/3/attachment",
"Created": "2024-05-10T08:53:12.409+00:00",
"EntryType": "Event",
"Id": "3",
"Message": "xyz.openbmc_project.State.Cable.CableDisconnected",
"Modified": "2024-05-10T08:53:12.409+00:00",
"Name": "System Event Log Entry",
"Resolved": false,
"Severity": "Warning"
}
],
"Members@odata.count": 2,
"Name": "System Event Log Entries"
}
>> curl -k -H "X-Auth-Token: $token" -X GET https://${bmc}/redfish/v1/Systems/system/LogServices/EventLog/Entries
{
"@odata.id": "/redfish/v1/Systems/system/LogServices/EventLog/Entries",
"@odata.type": "#LogEntryCollection.LogEntryCollection",
"Description": "Collection of System Event Log Entries",
"Members": [
...
{
"@odata.id": "/redfish/v1/Systems/system/LogServices/EventLog/Entries/3",
"@odata.type": "#LogEntry.v1_9_0.LogEntry",
"AdditionalDataURI": "/redfish/v1/Systems/system/LogServices/EventLog/Entries/3/attachment",
"Created": "2024-05-10T08:53:12.409+00:00",
"EntryType": "Event",
"Id": "3",
"Message": "xyz.openbmc_project.State.Cable.CableDisconnected",
"Modified": "2024-05-10T08:54:13.691+00:00",
"Name": "System Event Log Entry",
"Resolved": true,
"Severity": "Warning"
},
{
"@odata.id": "/redfish/v1/Systems/system/LogServices/EventLog/Entries/5",
"@odata.type": "#LogEntry.v1_9_0.LogEntry",
"AdditionalDataURI": "/redfish/v1/Systems/system/LogServices/EventLog/Entries/5/attachment",
"Created": "2024-05-10T08:54:13.743+00:00",
"EntryType": "Event",
"Id": "5",
"Message": "xyz.openbmc_project.State.Cable.CableConnected",
"Modified": "2024-05-10T08:54:13.743+00:00",
"Name": "System Event Log Entry",
"Resolved": false,
"Severity": "OK"
}
],
"Members@odata.count": 4,
"Name": "System Event Log Entries"
}
```
Change-Id: Ic0552962406a95cc46d4dd1d83d72e68e0bd28b4
Signed-off-by: Jagpal Singh Gill <paligill@gmail.com>
diff --git a/src/NotifyWatch.cpp b/src/NotifyWatch.cpp
new file mode 100644
index 0000000..d4d8faf
--- /dev/null
+++ b/src/NotifyWatch.cpp
@@ -0,0 +1,118 @@
+#include "NotifyWatch.hpp"
+
+#include <sys/inotify.h>
+#include <unistd.h>
+
+#include <sdbusplus/async.hpp>
+
+#include <array>
+#include <cerrno>
+#include <cstdint>
+#include <cstring>
+#include <filesystem>
+#include <memory>
+#include <span>
+#include <string>
+#include <system_error>
+#include <utility>
+
+namespace notify_watch
+{
+
+namespace fs = std::filesystem;
+
+NotifyWatch::NotifyWatch(sdbusplus::async::context& ctx, const std::string& dir,
+ Callback_t callback) :
+ ctx(ctx), callback(std::move(callback))
+{
+ std::error_code ec = {};
+
+ fs::path dirPath(dir);
+ if (!fs::create_directories(dirPath, ec))
+ {
+ if (ec)
+ {
+ throw std::system_error(ec, "Failed to create directory " + dir);
+ }
+ }
+
+ fd = inotify_init1(IN_NONBLOCK);
+ if (-1 == fd)
+ {
+ throw std::system_error(errno, std::system_category(),
+ "inotify_init1 failed");
+ }
+
+ wd = inotify_add_watch(fd, dir.c_str(), IN_CLOSE_WRITE);
+ if (-1 == wd)
+ {
+ close(fd);
+ throw std::system_error(errno, std::system_category(),
+ "inotify_add_watch failed");
+ }
+
+ fdioInstance = std::make_unique<sdbusplus::async::fdio>(ctx, fd);
+ if (!fdioInstance)
+ {
+ throw std::system_error(errno, std::system_category(),
+ "Failed to create fdio");
+ }
+}
+
+NotifyWatch::~NotifyWatch()
+{
+ if (-1 != fd)
+ {
+ if (-1 != wd)
+ {
+ inotify_rm_watch(fd, wd);
+ }
+ close(fd);
+ }
+}
+
+auto NotifyWatch::readNotifyAsync() -> sdbusplus::async::task<>
+{
+ if (!fdioInstance)
+ {
+ co_return;
+ }
+ co_await fdioInstance->next();
+
+ alignas(inotify_event) std::array<uint8_t, 4096> buffer{};
+
+ auto bytes = read(fd, buffer.data(), buffer.size());
+ if (bytes < 0)
+ {
+ throw std::system_error(errno, std::system_category(),
+ "Failed to read notify event");
+ }
+
+ for (auto* iter = buffer.data(); iter < buffer.data() + bytes;)
+ {
+ // Bypassed clang tidy warning about reinterpret_cast as cast is being
+ // performed to avoid copying of buffer data.
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+ std::span<inotify_event> event{reinterpret_cast<inotify_event*>(iter),
+ 1};
+ if (((event[0].mask & IN_CLOSE_WRITE) != 0U) &&
+ ((event[0].mask & IN_ISDIR) == 0U))
+ {
+ if (callback)
+ {
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+ std::span<char> name{reinterpret_cast<char*>(event[0].name),
+ event[0].len};
+ co_await callback(std::string(name.begin(), name.end()));
+ }
+ }
+ iter += sizeof(inotify_event) + event[0].len;
+ }
+
+ if (!ctx.stop_requested())
+ {
+ ctx.spawn(readNotifyAsync());
+ }
+}
+
+} // namespace notify_watch