xref: /openbmc/phosphor-bmc-code-mgmt/common/include/NotifyWatch.hpp (revision f2c95a08ad4340d8e8e7ccdb1af9f5a45d180530)
1 #pragma once
2 #include <sys/inotify.h>
3 #include <unistd.h>
4 
5 #include <sdbusplus/async/context.hpp>
6 #include <sdbusplus/async/fdio.hpp>
7 #include <sdbusplus/async/task.hpp>
8 
9 #include <array>
10 #include <cerrno>
11 #include <cstddef>
12 #include <cstdint>
13 #include <cstring>
14 #include <filesystem>
15 #include <memory>
16 #include <span>
17 #include <string>
18 #include <system_error>
19 namespace phosphor::notify::watch
20 {
21 namespace fs = std::filesystem;
22 template <typename Instance>
23 class NotifyWatch
24 {
25   public:
26     NotifyWatch() = delete;
27     NotifyWatch(const NotifyWatch&) = delete;
28     NotifyWatch& operator=(const NotifyWatch&) = delete;
29     NotifyWatch(NotifyWatch&&) = delete;
30     NotifyWatch& operator=(NotifyWatch&&) = delete;
31 
NotifyWatch(sdbusplus::async::context & ctx,const std::string & dir)32     explicit NotifyWatch(sdbusplus::async::context& ctx,
33                          const std::string& dir) : notifyCtx(ctx)
34     {
35         std::error_code ec = {};
36         fs::path dirPath(dir);
37         if (!fs::create_directories(dirPath, ec))
38         {
39             if (ec)
40             {
41                 throw std::system_error(ec,
42                                         "Failed to create directory " + dir);
43             }
44         }
45         fd = inotify_init1(IN_NONBLOCK);
46         if (-1 == fd)
47         {
48             throw std::system_error(errno, std::system_category(),
49                                     "inotify_init1 failed");
50         }
51         wd = inotify_add_watch(fd, dir.c_str(), IN_CLOSE_WRITE);
52         if (-1 == wd)
53         {
54             close(fd);
55             throw std::system_error(errno, std::system_category(),
56                                     "inotify_add_watch failed");
57         }
58         fdioInstance = std::make_unique<sdbusplus::async::fdio>(ctx, fd);
59     }
~NotifyWatch()60     ~NotifyWatch()
61     {
62         if (-1 != fd)
63         {
64             if (-1 != wd)
65             {
66                 inotify_rm_watch(fd, wd);
67             }
68             close(fd);
69         }
70     }
readNotifyAsync()71     sdbusplus::async::task<> readNotifyAsync()
72     {
73         co_await fdioInstance->next();
74         constexpr size_t maxBytes = 1024;
75         std::array<uint8_t, maxBytes> buffer{};
76         auto bytes = read(fd, buffer.data(), maxBytes);
77         if (0 > bytes)
78         {
79             throw std::system_error(errno, std::system_category(),
80                                     "Failed to read notify event");
81         }
82         auto offset = 0;
83         while (offset < bytes)
84         {
85             // NOLINTBEGIN(cppcoreguidelines-pro-type-reinterpret-cast)
86             std::span<uint32_t> mask{
87                 reinterpret_cast<uint32_t*>(
88                     buffer.data() + offset + offsetof(inotify_event, mask)),
89                 1};
90             std::span<uint32_t> len{
91                 reinterpret_cast<uint32_t*>(
92                     buffer.data() + offset + offsetof(inotify_event, len)),
93                 1};
94             // NOLINTEND(cppcoreguidelines-pro-type-reinterpret-cast)
95             if (((mask[0] & IN_CLOSE_WRITE) != 0U) &&
96                 ((mask[0] & IN_ISDIR) == 0U))
97             {
98                 // NOLINTBEGIN(cppcoreguidelines-pro-type-reinterpret-cast)
99                 std::span<char> name{
100                     reinterpret_cast<char*>(
101                         buffer.data() + offset + offsetof(inotify_event, name)),
102                     len[0]};
103                 // NOLINTEND(cppcoreguidelines-pro-type-reinterpret-cast)
104                 co_await static_cast<Instance*>(this)->processUpdate(
105                     std::string(name.begin(), name.end()));
106             }
107             offset += offsetof(inotify_event, name) + len[0];
108         }
109         if (!notifyCtx.stop_requested())
110         {
111             notifyCtx.spawn(readNotifyAsync());
112         }
113     }
114 
115   private:
116     sdbusplus::async::context& notifyCtx;
117     int wd = -1;
118     int fd = -1;
119     std::unique_ptr<sdbusplus::async::fdio> fdioInstance;
120 };
121 } // namespace phosphor::notify::watch
122