1 #include "filesystem_log_watcher.hpp"
2 
3 #include "event_log.hpp"
4 #include "event_logs_object_type.hpp"
5 #include "event_service_manager.hpp"
6 #include "logging.hpp"
7 
8 #include <sys/inotify.h>
9 
10 #include <boost/asio/buffer.hpp>
11 #include <boost/asio/error.hpp>
12 #include <boost/asio/io_context.hpp>
13 #include <boost/asio/posix/stream_descriptor.hpp>
14 
15 #include <array>
16 #include <cstddef>
17 #include <cstring>
18 #include <fstream>
19 #include <functional>
20 #include <string>
21 #include <vector>
22 
23 namespace redfish
24 {
25 void FilesystemLogWatcher::resetRedfishFilePosition()
26 {
27     // Control would be here when Redfish file is created.
28     // Reset File Position as new file is created
29     redfishLogFilePosition = 0;
30 }
31 
32 void FilesystemLogWatcher::cacheRedfishLogFile()
33 {
34     // Open the redfish file and read till the last record.
35 
36     std::ifstream logStream(redfishEventLogFile);
37     if (!logStream.good())
38     {
39         BMCWEB_LOG_ERROR(" Redfish log file open failed ");
40         return;
41     }
42     std::string logEntry;
43     while (std::getline(logStream, logEntry))
44     {
45         redfishLogFilePosition = logStream.tellg();
46     }
47 }
48 
49 void FilesystemLogWatcher::readEventLogsFromFile()
50 {
51     std::ifstream logStream(redfishEventLogFile);
52     if (!logStream.good())
53     {
54         BMCWEB_LOG_ERROR(" Redfish log file open failed");
55         return;
56     }
57 
58     std::vector<EventLogObjectsType> eventRecords;
59 
60     std::string logEntry;
61 
62     BMCWEB_LOG_DEBUG("Redfish log file: seek to {}",
63                      static_cast<int>(redfishLogFilePosition));
64 
65     // Get the read pointer to the next log to be read.
66     logStream.seekg(redfishLogFilePosition);
67 
68     while (std::getline(logStream, logEntry))
69     {
70         BMCWEB_LOG_DEBUG("Redfish log file: found new event log entry");
71         // Update Pointer position
72         redfishLogFilePosition = logStream.tellg();
73 
74         std::string idStr;
75         if (!event_log::getUniqueEntryID(logEntry, idStr))
76         {
77             BMCWEB_LOG_DEBUG(
78                 "Redfish log file: could not get unique entry id for {}",
79                 logEntry);
80             continue;
81         }
82 
83         std::string timestamp;
84         std::string messageID;
85         std::vector<std::string> messageArgs;
86         if (event_log::getEventLogParams(logEntry, timestamp, messageID,
87                                          messageArgs) != 0)
88         {
89             BMCWEB_LOG_DEBUG("Read eventLog entry params failed for {}",
90                              logEntry);
91             continue;
92         }
93 
94         eventRecords.emplace_back(idStr, timestamp, messageID, messageArgs);
95     }
96 
97     if (eventRecords.empty())
98     {
99         // No Records to send
100         BMCWEB_LOG_DEBUG("No log entries available to be transferred.");
101         return;
102     }
103     EventServiceManager::sendEventsToSubs(eventRecords);
104 }
105 
106 static constexpr const char* redfishEventLogDir = "/var/log";
107 static constexpr const size_t iEventSize = sizeof(inotify_event);
108 
109 void FilesystemLogWatcher::onINotify(const boost::system::error_code& ec,
110                                      std::size_t bytesTransferred)
111 {
112     if (ec == boost::asio::error::operation_aborted)
113     {
114         BMCWEB_LOG_DEBUG("Inotify was canceled (shutdown?)");
115         return;
116     }
117     if (ec)
118     {
119         BMCWEB_LOG_ERROR("Callback Error: {}", ec.message());
120         return;
121     }
122 
123     BMCWEB_LOG_DEBUG("reading {} via inotify", bytesTransferred);
124 
125     std::size_t index = 0;
126     while ((index + iEventSize) <= bytesTransferred)
127     {
128         struct inotify_event event
129         {};
130         std::memcpy(&event, &readBuffer[index], iEventSize);
131         if (event.wd == dirWatchDesc)
132         {
133             if ((event.len == 0) ||
134                 (index + iEventSize + event.len > bytesTransferred))
135             {
136                 index += (iEventSize + event.len);
137                 continue;
138             }
139 
140             std::string fileName(&readBuffer[index + iEventSize]);
141             if (fileName != "redfish")
142             {
143                 index += (iEventSize + event.len);
144                 continue;
145             }
146 
147             BMCWEB_LOG_DEBUG("Redfish log file created/deleted. event.name: {}",
148                              fileName);
149             if (event.mask == IN_CREATE)
150             {
151                 if (fileWatchDesc != -1)
152                 {
153                     BMCWEB_LOG_DEBUG("Remove and Add inotify watcher on "
154                                      "redfish event log file");
155                     // Remove existing inotify watcher and add
156                     // with new redfish event log file.
157                     inotify_rm_watch(inotifyFd, fileWatchDesc);
158                     fileWatchDesc = -1;
159                 }
160 
161                 fileWatchDesc = inotify_add_watch(
162                     inotifyFd, redfishEventLogFile, IN_MODIFY);
163                 if (fileWatchDesc == -1)
164                 {
165                     BMCWEB_LOG_ERROR("inotify_add_watch failed for "
166                                      "redfish log file.");
167                     return;
168                 }
169 
170                 resetRedfishFilePosition();
171                 readEventLogsFromFile();
172             }
173             else if ((event.mask == IN_DELETE) || (event.mask == IN_MOVED_TO))
174             {
175                 if (fileWatchDesc != -1)
176                 {
177                     inotify_rm_watch(inotifyFd, fileWatchDesc);
178                     fileWatchDesc = -1;
179                 }
180             }
181         }
182         else if (event.wd == fileWatchDesc)
183         {
184             if (event.mask == IN_MODIFY)
185             {
186                 readEventLogsFromFile();
187             }
188         }
189         index += (iEventSize + event.len);
190     }
191 
192     watchRedfishEventLogFile();
193 }
194 
195 void FilesystemLogWatcher::watchRedfishEventLogFile()
196 {
197     inotifyConn.async_read_some(
198         boost::asio::buffer(readBuffer),
199         std::bind_front(&FilesystemLogWatcher::onINotify, this));
200 }
201 
202 FilesystemLogWatcher::FilesystemLogWatcher(boost::asio::io_context& ioc) :
203     inotifyFd(inotify_init1(IN_NONBLOCK)), inotifyConn(ioc)
204 {
205     BMCWEB_LOG_DEBUG("starting Event Log Monitor");
206 
207     if (inotifyFd == -1)
208     {
209         BMCWEB_LOG_ERROR("inotify_init1 failed.");
210         return;
211     }
212 
213     // Add watch on directory to handle redfish event log file
214     // create/delete.
215     dirWatchDesc = inotify_add_watch(inotifyFd, redfishEventLogDir,
216                                      IN_CREATE | IN_MOVED_TO | IN_DELETE);
217     if (dirWatchDesc == -1)
218     {
219         BMCWEB_LOG_ERROR("inotify_add_watch failed for event log directory.");
220         return;
221     }
222 
223     // Watch redfish event log file for modifications.
224     fileWatchDesc =
225         inotify_add_watch(inotifyFd, redfishEventLogFile, IN_MODIFY);
226     if (fileWatchDesc == -1)
227     {
228         BMCWEB_LOG_ERROR("inotify_add_watch failed for redfish log file.");
229         // Don't return error if file not exist.
230         // Watch on directory will handle create/delete of file.
231     }
232 
233     // monitor redfish event log file
234     inotifyConn.assign(inotifyFd);
235     watchRedfishEventLogFile();
236 
237     if (redfishLogFilePosition != 0)
238     {
239         cacheRedfishLogFile();
240     }
241 }
242 } // namespace redfish
243