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