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