1 #include "watch.hpp"
2 
3 #include <sys/epoll.h>
4 #include <sys/inotify.h>
5 #include <unistd.h>
6 
7 #include <array>
8 #include <cerrno>
9 #include <climits>
10 #include <cstdint>
11 #include <cstring>
12 #include <filesystem>
13 #include <phosphor-logging/elog-errors.hpp>
14 #include <phosphor-logging/elog.hpp>
15 #include <phosphor-logging/log.hpp>
16 #include <sdeventplus/source/io.hpp>
17 #include <xyz/openbmc_project/Common/error.hpp>
18 
19 namespace phosphor::certs
20 {
21 
22 using ::phosphor::logging::elog;
23 using ::phosphor::logging::entry;
24 using ::phosphor::logging::level;
25 using ::phosphor::logging::log;
26 using ::sdbusplus::xyz::openbmc_project::Common::Error::InternalFailure;
27 namespace fs = std::filesystem;
28 
29 Watch::Watch(sdeventplus::Event& event, std::string& certFile, Callback cb) :
30     event(event), callback(std::move(cb))
31 {
32     // get parent directory of certificate file to watch
33     fs::path path = fs::path(certFile).parent_path();
34     try
35     {
36         if (!fs::exists(path))
37         {
38             fs::create_directories(path);
39         }
40     }
41     catch (const fs::filesystem_error& e)
42     {
43         log<level::ERR>("Failed to create directory", entry("ERR=%s", e.what()),
44                         entry("DIRECTORY=%s", path.c_str()));
45         elog<InternalFailure>();
46     }
47     watchDir = path;
48     watchFile = fs::path(certFile).filename();
49     startWatch();
50 }
51 
52 Watch::~Watch()
53 {
54     stopWatch();
55 }
56 
57 void Watch::startWatch()
58 {
59     // stop any existing watch
60     stopWatch();
61 
62     fd = inotify_init1(IN_NONBLOCK);
63     if (-1 == fd)
64     {
65         log<level::ERR>("inotify_init1 failed,",
66                         entry("ERR=%s", std::strerror(errno)));
67         elog<InternalFailure>();
68     }
69     wd = inotify_add_watch(fd, watchDir.c_str(), IN_CLOSE_WRITE);
70     if (-1 == wd)
71     {
72         close(fd);
73         log<level::ERR>("inotify_add_watch failed,",
74                         entry("ERR=%s", std::strerror(errno)),
75                         entry("WATCH=%s", watchDir.c_str()));
76         elog<InternalFailure>();
77     }
78 
79     ioPtr = std::make_unique<sdeventplus::source::IO>(
80         event, fd, EPOLLIN, [this](sdeventplus::source::IO&, int fd, uint32_t) {
81             constexpr int size = sizeof(struct inotify_event) + NAME_MAX + 1;
82             std::array<char, size> buffer{};
83             int length = read(fd, buffer.data(), buffer.size());
84             if (length >= static_cast<int>(sizeof(struct inotify_event)))
85             {
86                 struct inotify_event* notifyEvent =
87                     reinterpret_cast<struct inotify_event*>(&buffer[0]);
88                 if (notifyEvent->len)
89                 {
90                     if (watchFile == notifyEvent->name)
91                     {
92                         callback();
93                     }
94                 }
95             }
96             else
97             {
98                 log<level::ERR>("Failed to read inotify event");
99             }
100         });
101 }
102 
103 void Watch::stopWatch()
104 {
105     if (-1 != fd)
106     {
107         if (-1 != wd)
108         {
109             inotify_rm_watch(fd, wd);
110         }
111         close(fd);
112     }
113     if (ioPtr)
114     {
115         ioPtr.reset();
116     }
117 }
118 
119 } // namespace phosphor::certs
120