1 /*
2 Copyright (c) 2020 Intel Corporation
3 
4 Licensed under the Apache License, Version 2.0 (the "License");
5 you may not use this file except in compliance with the License.
6 You may obtain a copy of the License at
7 
8       http://www.apache.org/licenses/LICENSE-2.0
9 
10 Unless required by applicable law or agreed to in writing, software
11 distributed under the License is distributed on an "AS IS" BASIS,
12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 See the License for the specific language governing permissions and
14 limitations under the License.
15 */
16 #pragma once
17 #include "dbus_utility.hpp"
18 #include "error_messages.hpp"
19 #include "event_log.hpp"
20 #include "event_matches_filter.hpp"
21 #include "event_service_store.hpp"
22 #include "metric_report.hpp"
23 #include "ossl_random.hpp"
24 #include "persistent_data.hpp"
25 #include "subscription.hpp"
26 #include "utils/time_utils.hpp"
27 
28 #include <sys/inotify.h>
29 
30 #include <boost/asio/io_context.hpp>
31 #include <boost/circular_buffer.hpp>
32 #include <boost/container/flat_map.hpp>
33 #include <boost/url/format.hpp>
34 #include <boost/url/url_view_base.hpp>
35 #include <sdbusplus/bus/match.hpp>
36 
37 #include <algorithm>
38 #include <cstdlib>
39 #include <ctime>
40 #include <format>
41 #include <fstream>
42 #include <memory>
43 #include <string>
44 #include <string_view>
45 #include <utility>
46 
47 namespace redfish
48 {
49 
50 static constexpr const char* eventFormatType = "Event";
51 static constexpr const char* metricReportFormatType = "MetricReport";
52 
53 static constexpr const char* eventServiceFile =
54     "/var/lib/bmcweb/eventservice_config.json";
55 
56 // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
57 static std::optional<boost::asio::posix::stream_descriptor> inotifyConn;
58 static constexpr const char* redfishEventLogDir = "/var/log";
59 static constexpr const char* redfishEventLogFile = "/var/log/redfish";
60 static constexpr const size_t iEventSize = sizeof(inotify_event);
61 
62 // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
63 static int inotifyFd = -1;
64 // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
65 static int dirWatchDesc = -1;
66 // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
67 static int fileWatchDesc = -1;
68 
69 class EventServiceManager
70 {
71   private:
72     bool serviceEnabled = false;
73     uint32_t retryAttempts = 0;
74     uint32_t retryTimeoutInterval = 0;
75 
76     std::streampos redfishLogFilePosition{0};
77     size_t noOfEventLogSubscribers{0};
78     size_t noOfMetricReportSubscribers{0};
79     std::shared_ptr<sdbusplus::bus::match_t> matchTelemetryMonitor;
80     boost::container::flat_map<std::string, std::shared_ptr<Subscription>>
81         subscriptionsMap;
82 
83     uint64_t eventId{1};
84 
85     struct Event
86     {
87         std::string id;
88         nlohmann::json message;
89     };
90 
91     constexpr static size_t maxMessages = 200;
92     boost::circular_buffer<Event> messages{maxMessages};
93 
94     boost::asio::io_context& ioc;
95 
96   public:
97     EventServiceManager(const EventServiceManager&) = delete;
98     EventServiceManager& operator=(const EventServiceManager&) = delete;
99     EventServiceManager(EventServiceManager&&) = delete;
100     EventServiceManager& operator=(EventServiceManager&&) = delete;
101     ~EventServiceManager() = default;
102 
EventServiceManager(boost::asio::io_context & iocIn)103     explicit EventServiceManager(boost::asio::io_context& iocIn) : ioc(iocIn)
104     {
105         // Load config from persist store.
106         initConfig();
107     }
108 
109     static EventServiceManager&
getInstance(boost::asio::io_context * ioc=nullptr)110         getInstance(boost::asio::io_context* ioc = nullptr)
111     {
112         static EventServiceManager handler(*ioc);
113         return handler;
114     }
115 
initConfig()116     void initConfig()
117     {
118         loadOldBehavior();
119 
120         persistent_data::EventServiceConfig eventServiceConfig =
121             persistent_data::EventServiceStore::getInstance()
122                 .getEventServiceConfig();
123 
124         serviceEnabled = eventServiceConfig.enabled;
125         retryAttempts = eventServiceConfig.retryAttempts;
126         retryTimeoutInterval = eventServiceConfig.retryTimeoutInterval;
127 
128         for (const auto& it : persistent_data::EventServiceStore::getInstance()
129                                   .subscriptionsConfigMap)
130         {
131             std::shared_ptr<persistent_data::UserSubscription> newSub =
132                 it.second;
133 
134             boost::system::result<boost::urls::url> url =
135                 boost::urls::parse_absolute_uri(newSub->destinationUrl);
136 
137             if (!url)
138             {
139                 BMCWEB_LOG_ERROR(
140                     "Failed to validate and split destination url");
141                 continue;
142             }
143             std::shared_ptr<Subscription> subValue =
144                 std::make_shared<Subscription>(newSub, *url, ioc);
145             std::string id = subValue->userSub->id;
146             subValue->deleter = [id]() {
147                 EventServiceManager::getInstance().deleteSubscription(id);
148             };
149 
150             subscriptionsMap.emplace(id, subValue);
151 
152             updateNoOfSubscribersCount();
153 
154             if constexpr (!BMCWEB_REDFISH_DBUS_LOG)
155             {
156                 cacheRedfishLogFile();
157             }
158 
159             // Update retry configuration.
160             subValue->updateRetryConfig(retryAttempts, retryTimeoutInterval);
161         }
162     }
163 
loadOldBehavior()164     static void loadOldBehavior()
165     {
166         std::ifstream eventConfigFile(eventServiceFile);
167         if (!eventConfigFile.good())
168         {
169             BMCWEB_LOG_DEBUG("Old eventService config not exist");
170             return;
171         }
172         auto jsonData = nlohmann::json::parse(eventConfigFile, nullptr, false);
173         if (jsonData.is_discarded())
174         {
175             BMCWEB_LOG_ERROR("Old eventService config parse error.");
176             return;
177         }
178 
179         const nlohmann::json::object_t* obj =
180             jsonData.get_ptr<const nlohmann::json::object_t*>();
181         for (const auto& item : *obj)
182         {
183             if (item.first == "Configuration")
184             {
185                 persistent_data::EventServiceStore::getInstance()
186                     .getEventServiceConfig()
187                     .fromJson(item.second);
188             }
189             else if (item.first == "Subscriptions")
190             {
191                 for (const auto& elem : item.second)
192                 {
193                     std::optional<persistent_data::UserSubscription>
194                         newSubscription =
195                             persistent_data::UserSubscription::fromJson(elem,
196                                                                         true);
197                     if (!newSubscription)
198                     {
199                         BMCWEB_LOG_ERROR("Problem reading subscription "
200                                          "from old persistent store");
201                         continue;
202                     }
203                     persistent_data::UserSubscription& newSub =
204                         *newSubscription;
205 
206                     std::uniform_int_distribution<uint32_t> dist(0);
207                     bmcweb::OpenSSLGenerator gen;
208 
209                     std::string id;
210 
211                     int retry = 3;
212                     while (retry != 0)
213                     {
214                         id = std::to_string(dist(gen));
215                         if (gen.error())
216                         {
217                             retry = 0;
218                             break;
219                         }
220                         newSub.id = id;
221                         auto inserted =
222                             persistent_data::EventServiceStore::getInstance()
223                                 .subscriptionsConfigMap.insert(std::pair(
224                                     id, std::make_shared<
225                                             persistent_data::UserSubscription>(
226                                             newSub)));
227                         if (inserted.second)
228                         {
229                             break;
230                         }
231                         --retry;
232                     }
233 
234                     if (retry <= 0)
235                     {
236                         BMCWEB_LOG_ERROR(
237                             "Failed to generate random number from old "
238                             "persistent store");
239                         continue;
240                     }
241                 }
242             }
243 
244             persistent_data::getConfig().writeData();
245             std::error_code ec;
246             std::filesystem::remove(eventServiceFile, ec);
247             if (ec)
248             {
249                 BMCWEB_LOG_DEBUG(
250                     "Failed to remove old event service file.  Ignoring");
251             }
252             else
253             {
254                 BMCWEB_LOG_DEBUG("Remove old eventservice config");
255             }
256         }
257     }
258 
updateSubscriptionData() const259     void updateSubscriptionData() const
260     {
261         persistent_data::EventServiceStore::getInstance()
262             .eventServiceConfig.enabled = serviceEnabled;
263         persistent_data::EventServiceStore::getInstance()
264             .eventServiceConfig.retryAttempts = retryAttempts;
265         persistent_data::EventServiceStore::getInstance()
266             .eventServiceConfig.retryTimeoutInterval = retryTimeoutInterval;
267 
268         persistent_data::getConfig().writeData();
269     }
270 
setEventServiceConfig(const persistent_data::EventServiceConfig & cfg)271     void setEventServiceConfig(const persistent_data::EventServiceConfig& cfg)
272     {
273         bool updateConfig = false;
274         bool updateRetryCfg = false;
275 
276         if (serviceEnabled != cfg.enabled)
277         {
278             serviceEnabled = cfg.enabled;
279             if (serviceEnabled && noOfMetricReportSubscribers != 0U)
280             {
281                 registerMetricReportSignal();
282             }
283             else
284             {
285                 unregisterMetricReportSignal();
286             }
287             updateConfig = true;
288         }
289 
290         if (retryAttempts != cfg.retryAttempts)
291         {
292             retryAttempts = cfg.retryAttempts;
293             updateConfig = true;
294             updateRetryCfg = true;
295         }
296 
297         if (retryTimeoutInterval != cfg.retryTimeoutInterval)
298         {
299             retryTimeoutInterval = cfg.retryTimeoutInterval;
300             updateConfig = true;
301             updateRetryCfg = true;
302         }
303 
304         if (updateConfig)
305         {
306             updateSubscriptionData();
307         }
308 
309         if (updateRetryCfg)
310         {
311             // Update the changed retry config to all subscriptions
312             for (const auto& it :
313                  EventServiceManager::getInstance().subscriptionsMap)
314             {
315                 Subscription& entry = *it.second;
316                 entry.updateRetryConfig(retryAttempts, retryTimeoutInterval);
317             }
318         }
319     }
320 
updateNoOfSubscribersCount()321     void updateNoOfSubscribersCount()
322     {
323         size_t eventLogSubCount = 0;
324         size_t metricReportSubCount = 0;
325         for (const auto& it : subscriptionsMap)
326         {
327             std::shared_ptr<Subscription> entry = it.second;
328             if (entry->userSub->eventFormatType == eventFormatType)
329             {
330                 eventLogSubCount++;
331             }
332             else if (entry->userSub->eventFormatType == metricReportFormatType)
333             {
334                 metricReportSubCount++;
335             }
336         }
337 
338         noOfEventLogSubscribers = eventLogSubCount;
339         if (noOfMetricReportSubscribers != metricReportSubCount)
340         {
341             noOfMetricReportSubscribers = metricReportSubCount;
342             if (noOfMetricReportSubscribers != 0U)
343             {
344                 registerMetricReportSignal();
345             }
346             else
347             {
348                 unregisterMetricReportSignal();
349             }
350         }
351     }
352 
getSubscription(const std::string & id)353     std::shared_ptr<Subscription> getSubscription(const std::string& id)
354     {
355         auto obj = subscriptionsMap.find(id);
356         if (obj == subscriptionsMap.end())
357         {
358             BMCWEB_LOG_ERROR("No subscription exist with ID:{}", id);
359             return nullptr;
360         }
361         std::shared_ptr<Subscription> subValue = obj->second;
362         return subValue;
363     }
364 
365     std::string
addSubscriptionInternal(const std::shared_ptr<Subscription> & subValue)366         addSubscriptionInternal(const std::shared_ptr<Subscription>& subValue)
367     {
368         std::uniform_int_distribution<uint32_t> dist(0);
369         bmcweb::OpenSSLGenerator gen;
370 
371         std::string id;
372 
373         int retry = 3;
374         while (retry != 0)
375         {
376             id = std::to_string(dist(gen));
377             if (gen.error())
378             {
379                 retry = 0;
380                 break;
381             }
382             auto inserted = subscriptionsMap.insert(std::pair(id, subValue));
383             if (inserted.second)
384             {
385                 break;
386             }
387             --retry;
388         }
389 
390         if (retry <= 0)
391         {
392             BMCWEB_LOG_ERROR("Failed to generate random number");
393             return "";
394         }
395 
396         // Set Subscription ID for back trace
397         subValue->userSub->id = id;
398 
399         persistent_data::EventServiceStore::getInstance()
400             .subscriptionsConfigMap.emplace(id, subValue->userSub);
401 
402         updateNoOfSubscribersCount();
403 
404         if constexpr (!BMCWEB_REDFISH_DBUS_LOG)
405         {
406             if (redfishLogFilePosition != 0)
407             {
408                 cacheRedfishLogFile();
409             }
410         }
411         // Update retry configuration.
412         subValue->updateRetryConfig(retryAttempts, retryTimeoutInterval);
413 
414         return id;
415     }
416 
417     std::string
addSSESubscription(const std::shared_ptr<Subscription> & subValue,std::string_view lastEventId)418         addSSESubscription(const std::shared_ptr<Subscription>& subValue,
419                            std::string_view lastEventId)
420     {
421         std::string id = addSubscriptionInternal(subValue);
422 
423         if (!lastEventId.empty())
424         {
425             BMCWEB_LOG_INFO("Attempting to find message for last id {}",
426                             lastEventId);
427             boost::circular_buffer<Event>::iterator lastEvent =
428                 std::find_if(messages.begin(), messages.end(),
429                              [&lastEventId](const Event& event) {
430                                  return event.id == lastEventId;
431                              });
432             // Can't find a matching ID
433             if (lastEvent == messages.end())
434             {
435                 nlohmann::json msg = messages::eventBufferExceeded();
436                 // If the buffer overloaded, send all messages.
437                 subValue->sendEventToSubscriber(msg);
438                 lastEvent = messages.begin();
439             }
440             else
441             {
442                 // Skip the last event the user already has
443                 lastEvent++;
444             }
445 
446             for (boost::circular_buffer<Event>::const_iterator event =
447                      lastEvent;
448                  lastEvent != messages.end(); lastEvent++)
449             {
450                 subValue->sendEventToSubscriber(event->message);
451             }
452         }
453         return id;
454     }
455 
456     std::string
addPushSubscription(const std::shared_ptr<Subscription> & subValue)457         addPushSubscription(const std::shared_ptr<Subscription>& subValue)
458     {
459         std::string id = addSubscriptionInternal(subValue);
460         subValue->deleter = [id]() {
461             EventServiceManager::getInstance().deleteSubscription(id);
462         };
463         updateSubscriptionData();
464         return id;
465     }
466 
isSubscriptionExist(const std::string & id)467     bool isSubscriptionExist(const std::string& id)
468     {
469         auto obj = subscriptionsMap.find(id);
470         return obj != subscriptionsMap.end();
471     }
472 
deleteSubscription(const std::string & id)473     bool deleteSubscription(const std::string& id)
474     {
475         auto obj = subscriptionsMap.find(id);
476         if (obj == subscriptionsMap.end())
477         {
478             BMCWEB_LOG_WARNING("Could not find subscription with id {}", id);
479             return false;
480         }
481         subscriptionsMap.erase(obj);
482         auto& event = persistent_data::EventServiceStore::getInstance();
483         auto persistentObj = event.subscriptionsConfigMap.find(id);
484         if (persistentObj == event.subscriptionsConfigMap.end())
485         {
486             BMCWEB_LOG_ERROR("Subscription wasn't in persistent data");
487             return true;
488         }
489         persistent_data::EventServiceStore::getInstance()
490             .subscriptionsConfigMap.erase(persistentObj);
491         updateNoOfSubscribersCount();
492         updateSubscriptionData();
493 
494         return true;
495     }
496 
deleteSseSubscription(const crow::sse_socket::Connection & thisConn)497     void deleteSseSubscription(const crow::sse_socket::Connection& thisConn)
498     {
499         for (auto it = subscriptionsMap.begin(); it != subscriptionsMap.end();)
500         {
501             std::shared_ptr<Subscription> entry = it->second;
502             bool entryIsThisConn = entry->matchSseId(thisConn);
503             if (entryIsThisConn)
504             {
505                 persistent_data::EventServiceStore::getInstance()
506                     .subscriptionsConfigMap.erase(entry->userSub->id);
507                 it = subscriptionsMap.erase(it);
508                 return;
509             }
510             it++;
511         }
512     }
513 
getNumberOfSubscriptions() const514     size_t getNumberOfSubscriptions() const
515     {
516         return subscriptionsMap.size();
517     }
518 
getNumberOfSSESubscriptions() const519     size_t getNumberOfSSESubscriptions() const
520     {
521         auto size = std::ranges::count_if(
522             subscriptionsMap,
523             [](const std::pair<std::string, std::shared_ptr<Subscription>>&
524                    entry) {
525                 return (entry.second->userSub->subscriptionType ==
526                         subscriptionTypeSSE);
527             });
528         return static_cast<size_t>(size);
529     }
530 
getAllIDs()531     std::vector<std::string> getAllIDs()
532     {
533         std::vector<std::string> idList;
534         for (const auto& it : subscriptionsMap)
535         {
536             idList.emplace_back(it.first);
537         }
538         return idList;
539     }
540 
sendTestEventLog()541     bool sendTestEventLog()
542     {
543         for (const auto& it : subscriptionsMap)
544         {
545             std::shared_ptr<Subscription> entry = it.second;
546             if (!entry->sendTestEventLog())
547             {
548                 return false;
549             }
550         }
551         return true;
552     }
553 
sendEvent(nlohmann::json::object_t eventMessage,std::string_view origin,std::string_view resourceType)554     void sendEvent(nlohmann::json::object_t eventMessage,
555                    std::string_view origin, std::string_view resourceType)
556     {
557         eventMessage["EventId"] = eventId;
558 
559         eventMessage["EventTimestamp"] =
560             redfish::time_utils::getDateTimeOffsetNow().first;
561         eventMessage["OriginOfCondition"] = origin;
562 
563         // MemberId is 0 : since we are sending one event record.
564         eventMessage["MemberId"] = "0";
565 
566         messages.push_back(Event(std::to_string(eventId), eventMessage));
567 
568         for (auto& it : subscriptionsMap)
569         {
570             std::shared_ptr<Subscription>& entry = it.second;
571             if (!eventMatchesFilter(*entry->userSub, eventMessage,
572                                     resourceType))
573             {
574                 BMCWEB_LOG_DEBUG("Filter didn't match");
575                 continue;
576             }
577 
578             nlohmann::json::array_t eventRecord;
579             eventRecord.emplace_back(eventMessage);
580 
581             nlohmann::json msgJson;
582 
583             msgJson["@odata.type"] = "#Event.v1_4_0.Event";
584             msgJson["Name"] = "Event Log";
585             msgJson["Id"] = eventId;
586             msgJson["Events"] = std::move(eventRecord);
587 
588             std::string strMsg = msgJson.dump(
589                 2, ' ', true, nlohmann::json::error_handler_t::replace);
590             entry->sendEventToSubscriber(std::move(strMsg));
591             eventId++; // increment the eventId
592         }
593     }
594 
resetRedfishFilePosition()595     void resetRedfishFilePosition()
596     {
597         // Control would be here when Redfish file is created.
598         // Reset File Position as new file is created
599         redfishLogFilePosition = 0;
600     }
601 
cacheRedfishLogFile()602     void cacheRedfishLogFile()
603     {
604         // Open the redfish file and read till the last record.
605 
606         std::ifstream logStream(redfishEventLogFile);
607         if (!logStream.good())
608         {
609             BMCWEB_LOG_ERROR(" Redfish log file open failed ");
610             return;
611         }
612         std::string logEntry;
613         while (std::getline(logStream, logEntry))
614         {
615             redfishLogFilePosition = logStream.tellg();
616         }
617     }
618 
readEventLogsFromFile()619     void readEventLogsFromFile()
620     {
621         std::ifstream logStream(redfishEventLogFile);
622         if (!logStream.good())
623         {
624             BMCWEB_LOG_ERROR(" Redfish log file open failed");
625             return;
626         }
627 
628         std::vector<EventLogObjectsType> eventRecords;
629 
630         std::string logEntry;
631 
632         BMCWEB_LOG_DEBUG("Redfish log file: seek to {}",
633                          static_cast<int>(redfishLogFilePosition));
634 
635         // Get the read pointer to the next log to be read.
636         logStream.seekg(redfishLogFilePosition);
637 
638         while (std::getline(logStream, logEntry))
639         {
640             BMCWEB_LOG_DEBUG("Redfish log file: found new event log entry");
641             // Update Pointer position
642             redfishLogFilePosition = logStream.tellg();
643 
644             std::string idStr;
645             if (!event_log::getUniqueEntryID(logEntry, idStr))
646             {
647                 BMCWEB_LOG_DEBUG(
648                     "Redfish log file: could not get unique entry id for {}",
649                     logEntry);
650                 continue;
651             }
652 
653             if (!serviceEnabled || noOfEventLogSubscribers == 0)
654             {
655                 // If Service is not enabled, no need to compute
656                 // the remaining items below.
657                 // But, Loop must continue to keep track of Timestamp
658                 BMCWEB_LOG_DEBUG(
659                     "Redfish log file: no subscribers / event service not enabled");
660                 continue;
661             }
662 
663             std::string timestamp;
664             std::string messageID;
665             std::vector<std::string> messageArgs;
666             if (event_log::getEventLogParams(logEntry, timestamp, messageID,
667                                              messageArgs) != 0)
668             {
669                 BMCWEB_LOG_DEBUG("Read eventLog entry params failed for {}",
670                                  logEntry);
671                 continue;
672             }
673 
674             eventRecords.emplace_back(idStr, timestamp, messageID, messageArgs);
675         }
676 
677         if (!serviceEnabled || noOfEventLogSubscribers == 0)
678         {
679             BMCWEB_LOG_DEBUG("EventService disabled or no Subscriptions.");
680             return;
681         }
682 
683         if (eventRecords.empty())
684         {
685             // No Records to send
686             BMCWEB_LOG_DEBUG("No log entries available to be transferred.");
687             return;
688         }
689 
690         for (const auto& it : subscriptionsMap)
691         {
692             std::shared_ptr<Subscription> entry = it.second;
693             if (entry->userSub->eventFormatType == "Event")
694             {
695                 entry->filterAndSendEventLogs(eventRecords);
696             }
697         }
698     }
699 
watchRedfishEventLogFile()700     static void watchRedfishEventLogFile()
701     {
702         if (!inotifyConn)
703         {
704             BMCWEB_LOG_ERROR("inotify Connection is not present");
705             return;
706         }
707 
708         static std::array<char, 1024> readBuffer;
709 
710         inotifyConn->async_read_some(
711             boost::asio::buffer(readBuffer),
712             [&](const boost::system::error_code& ec,
713                 const std::size_t& bytesTransferred) {
714                 if (ec == boost::asio::error::operation_aborted)
715                 {
716                     BMCWEB_LOG_DEBUG("Inotify was canceled (shutdown?)");
717                     return;
718                 }
719                 if (ec)
720                 {
721                     BMCWEB_LOG_ERROR("Callback Error: {}", ec.message());
722                     return;
723                 }
724 
725                 BMCWEB_LOG_DEBUG("reading {} via inotify", bytesTransferred);
726 
727                 std::size_t index = 0;
728                 while ((index + iEventSize) <= bytesTransferred)
729                 {
730                     struct inotify_event event
731                     {};
732                     std::memcpy(&event, &readBuffer[index], iEventSize);
733                     if (event.wd == dirWatchDesc)
734                     {
735                         if ((event.len == 0) ||
736                             (index + iEventSize + event.len > bytesTransferred))
737                         {
738                             index += (iEventSize + event.len);
739                             continue;
740                         }
741 
742                         std::string fileName(&readBuffer[index + iEventSize]);
743                         if (fileName != "redfish")
744                         {
745                             index += (iEventSize + event.len);
746                             continue;
747                         }
748 
749                         BMCWEB_LOG_DEBUG(
750                             "Redfish log file created/deleted. event.name: {}",
751                             fileName);
752                         if (event.mask == IN_CREATE)
753                         {
754                             if (fileWatchDesc != -1)
755                             {
756                                 BMCWEB_LOG_DEBUG(
757                                     "Remove and Add inotify watcher on "
758                                     "redfish event log file");
759                                 // Remove existing inotify watcher and add
760                                 // with new redfish event log file.
761                                 inotify_rm_watch(inotifyFd, fileWatchDesc);
762                                 fileWatchDesc = -1;
763                             }
764 
765                             fileWatchDesc = inotify_add_watch(
766                                 inotifyFd, redfishEventLogFile, IN_MODIFY);
767                             if (fileWatchDesc == -1)
768                             {
769                                 BMCWEB_LOG_ERROR("inotify_add_watch failed for "
770                                                  "redfish log file.");
771                                 return;
772                             }
773 
774                             EventServiceManager::getInstance()
775                                 .resetRedfishFilePosition();
776                             EventServiceManager::getInstance()
777                                 .readEventLogsFromFile();
778                         }
779                         else if ((event.mask == IN_DELETE) ||
780                                  (event.mask == IN_MOVED_TO))
781                         {
782                             if (fileWatchDesc != -1)
783                             {
784                                 inotify_rm_watch(inotifyFd, fileWatchDesc);
785                                 fileWatchDesc = -1;
786                             }
787                         }
788                     }
789                     else if (event.wd == fileWatchDesc)
790                     {
791                         if (event.mask == IN_MODIFY)
792                         {
793                             EventServiceManager::getInstance()
794                                 .readEventLogsFromFile();
795                         }
796                     }
797                     index += (iEventSize + event.len);
798                 }
799 
800                 watchRedfishEventLogFile();
801             });
802     }
803 
startEventLogMonitor(boost::asio::io_context & ioc)804     static int startEventLogMonitor(boost::asio::io_context& ioc)
805     {
806         BMCWEB_LOG_DEBUG("starting Event Log Monitor");
807 
808         inotifyConn.emplace(ioc);
809         inotifyFd = inotify_init1(IN_NONBLOCK);
810         if (inotifyFd == -1)
811         {
812             BMCWEB_LOG_ERROR("inotify_init1 failed.");
813             return -1;
814         }
815 
816         // Add watch on directory to handle redfish event log file
817         // create/delete.
818         dirWatchDesc = inotify_add_watch(inotifyFd, redfishEventLogDir,
819                                          IN_CREATE | IN_MOVED_TO | IN_DELETE);
820         if (dirWatchDesc == -1)
821         {
822             BMCWEB_LOG_ERROR(
823                 "inotify_add_watch failed for event log directory.");
824             return -1;
825         }
826 
827         // Watch redfish event log file for modifications.
828         fileWatchDesc =
829             inotify_add_watch(inotifyFd, redfishEventLogFile, IN_MODIFY);
830         if (fileWatchDesc == -1)
831         {
832             BMCWEB_LOG_ERROR("inotify_add_watch failed for redfish log file.");
833             // Don't return error if file not exist.
834             // Watch on directory will handle create/delete of file.
835         }
836 
837         // monitor redfish event log file
838         inotifyConn->assign(inotifyFd);
839         watchRedfishEventLogFile();
840 
841         return 0;
842     }
843 
stopEventLogMonitor()844     static void stopEventLogMonitor()
845     {
846         inotifyConn.reset();
847     }
848 
getReadingsForReport(sdbusplus::message_t & msg)849     static void getReadingsForReport(sdbusplus::message_t& msg)
850     {
851         if (msg.is_method_error())
852         {
853             BMCWEB_LOG_ERROR("TelemetryMonitor Signal error");
854             return;
855         }
856 
857         sdbusplus::message::object_path path(msg.get_path());
858         std::string id = path.filename();
859         if (id.empty())
860         {
861             BMCWEB_LOG_ERROR("Failed to get Id from path");
862             return;
863         }
864 
865         std::string interface;
866         dbus::utility::DBusPropertiesMap props;
867         std::vector<std::string> invalidProps;
868         msg.read(interface, props, invalidProps);
869 
870         auto found = std::ranges::find_if(props, [](const auto& x) {
871             return x.first == "Readings";
872         });
873         if (found == props.end())
874         {
875             BMCWEB_LOG_INFO("Failed to get Readings from Report properties");
876             return;
877         }
878 
879         const telemetry::TimestampReadings* readings =
880             std::get_if<telemetry::TimestampReadings>(&found->second);
881         if (readings == nullptr)
882         {
883             BMCWEB_LOG_INFO("Failed to get Readings from Report properties");
884             return;
885         }
886 
887         for (const auto& it :
888              EventServiceManager::getInstance().subscriptionsMap)
889         {
890             Subscription& entry = *it.second;
891             if (entry.userSub->eventFormatType == metricReportFormatType)
892             {
893                 entry.filterAndSendReports(id, *readings);
894             }
895         }
896     }
897 
unregisterMetricReportSignal()898     void unregisterMetricReportSignal()
899     {
900         if (matchTelemetryMonitor)
901         {
902             BMCWEB_LOG_DEBUG("Metrics report signal - Unregister");
903             matchTelemetryMonitor.reset();
904             matchTelemetryMonitor = nullptr;
905         }
906     }
907 
registerMetricReportSignal()908     void registerMetricReportSignal()
909     {
910         if (!serviceEnabled || matchTelemetryMonitor)
911         {
912             BMCWEB_LOG_DEBUG("Not registering metric report signal.");
913             return;
914         }
915 
916         BMCWEB_LOG_DEBUG("Metrics report signal - Register");
917         std::string matchStr = "type='signal',member='PropertiesChanged',"
918                                "interface='org.freedesktop.DBus.Properties',"
919                                "arg0=xyz.openbmc_project.Telemetry.Report";
920 
921         matchTelemetryMonitor = std::make_shared<sdbusplus::bus::match_t>(
922             *crow::connections::systemBus, matchStr, getReadingsForReport);
923     }
924 };
925 
926 } // namespace redfish
927