Repo-Init
Loading...
Searching...
No Matches
FileHelpers.cpp
Go to the documentation of this file.
2
4
5#include <format>
6
7#include <fcntl.h>
8#include <sys/file.h>
9#include <sys/ioctl.h>
10#include <unistd.h>
11
12#include <spdlog/spdlog.h>
13
15constexpr int SLEEP_INTERVAL_MS = 50;
16
17void FileMonitor::threadFunc(const std::stop_token &stopToken) const noexcept
18{
19 while (!stopToken.stop_requested())
20 {
21 // Buffer for reading events
22 unsigned int nBytes = 0;
23 if (ioctl(_fDescriptor, FIONREAD, &nBytes) < 0)
24 {
25 spdlog::error("Failed to get available events for file monitoring: {}", getErrnoString(errno));
26 }
27
28 auto buffer = std::vector<char>(nBytes + 1, '\0');
29 auto nRead = read(_fDescriptor, buffer.data(), nBytes);
30 if (nRead < 0 && errno != EAGAIN)
31 {
32 spdlog::error("Failed to read events for file monitoring: {}", getErrnoString(errno));
33 }
34 else if (nRead == 0)
35 {
36 spdlog::debug("No events read for file monitoring");
37 }
38
39 ssize_t idx = 0;
40 while (_notifyCallback && idx < nRead)
41 {
42 const auto *event = std::bit_cast<inotify_event *>(&buffer[static_cast<size_t>(idx)]);
43
44 // Check if file notify type matches
45 if ((event->mask & _notifyEvents) != 0)
46 {
48 break;
49 }
50
51 idx += static_cast<ssize_t>(sizeof(inotify_event) + event->len);
52 }
53
54 std::this_thread::sleep_for(std::chrono::milliseconds(SLEEP_INTERVAL_MS));
55 }
56}
57
58FileMonitor::FileMonitor(std::filesystem::path filePath, uint32_t notifyEvents)
59 : _fDescriptor(inotify_init()), _filePath(std::move(filePath)), _notifyEvents(notifyEvents)
60{
61 if (_fDescriptor < 0)
62 {
63 throw std::ios_base::failure("Failed to initialize inotify");
64 }
65
66 _wDescriptor = inotify_add_watch(_fDescriptor, _filePath.c_str(), notifyEvents);
67 if (_wDescriptor < 0)
68 {
69 close(_fDescriptor);
70 throw std::ios_base::failure("Failed to add watch descriptor");
71 }
72
73 if (const int flags = fcntl(_fDescriptor, F_GETFL);
74 flags < 0 || fcntl(_fDescriptor, F_SETFL, flags | O_NONBLOCK) < 0)
75 {
76 close(_fDescriptor);
77 throw std::ios_base::failure("Failed to set file descriptor to non-blocking mode");
78 }
79
80 _thread = std::make_unique<std::jthread>([this](const std::stop_token &sToken) { threadFunc(sToken); });
81}
82
84{
85 if (_thread)
86 {
87 _thread.reset();
88 }
89
90 // Remove watch descriptor first
91 if (_wDescriptor >= 0)
92 {
93 if (inotify_rm_watch(_fDescriptor, _wDescriptor) < 0)
94 {
95 try
96 {
97 spdlog::error("Failed to remove watch descriptor: {}", getErrnoString(errno));
98 }
99 catch (const std::exception &e)
100 {
101 const auto errStr = getErrnoString(errno);
102 (void)std::fprintf(stderr,
103 "Failed to remove watch descriptor and also logger thrown an exception: %s %s\n",
104 errStr.c_str(), e.what());
105 }
106 }
107 _wDescriptor = -1;
108 }
109
110 // Then close the file descriptor
111 if (_fDescriptor >= 0)
112 {
113 if (close(_fDescriptor) < 0)
114 {
115 try
116 {
117 spdlog::error("Failed to close file descriptor: {}", getErrnoString(errno));
118 }
119 catch (const std::exception &e)
120 {
121 const auto errStr = getErrnoString(errno);
122 (void)std::fprintf(stderr,
123 "Failed to close file descriptor and also logger thrown an exception: %s %s\n",
124 errStr.c_str(), e.what());
125 }
126 }
127 _fDescriptor = -1;
128 }
129}
std::string getErrnoString(int errVal)
constexpr int SLEEP_INTERVAL_MS
const void * _userPtr
User pointer.
void threadFunc(const std::stop_token &stopToken) const noexcept
FNotifyCallback _notifyCallback
Callback function.
int _fDescriptor
File descriptor.
FileMonitor(std::filesystem::path filePath, uint32_t notifyEvents=IN_MODIFY)
int _wDescriptor
Watch descriptor.
uint32_t _notifyEvents
Notify types.
std::filesystem::path _filePath
File path.
std::unique_ptr< std::jthread > _thread
Thread.