1 /**
2  * Copyright © 2019 IBM 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 #include "manager.hpp"
17 
18 #include "additional_data.hpp"
19 #include "json_utils.hpp"
20 #include "pel.hpp"
21 #include "pel_entry.hpp"
22 #include "service_indicators.hpp"
23 
24 #include <fmt/format.h>
25 #include <sys/inotify.h>
26 #include <unistd.h>
27 
28 #include <filesystem>
29 #include <fstream>
30 #include <xyz/openbmc_project/Common/error.hpp>
31 #include <xyz/openbmc_project/Logging/Create/server.hpp>
32 
33 namespace openpower
34 {
35 namespace pels
36 {
37 
38 using namespace phosphor::logging;
39 namespace fs = std::filesystem;
40 namespace rg = openpower::pels::message;
41 
42 namespace common_error = sdbusplus::xyz::openbmc_project::Common::Error;
43 
44 using Create = sdbusplus::xyz::openbmc_project::Logging::server::Create;
45 
46 namespace additional_data
47 {
48 constexpr auto rawPEL = "RAWPEL";
49 constexpr auto esel = "ESEL";
50 constexpr auto error = "ERROR_NAME";
51 } // namespace additional_data
52 
53 constexpr auto defaultLogMessage = "xyz.openbmc_project.Logging.Error.Default";
54 
55 Manager::~Manager()
56 {
57     if (_pelFileDeleteFD != -1)
58     {
59         if (_pelFileDeleteWatchFD != -1)
60         {
61             inotify_rm_watch(_pelFileDeleteFD, _pelFileDeleteWatchFD);
62         }
63         close(_pelFileDeleteFD);
64     }
65 }
66 
67 void Manager::create(const std::string& message, uint32_t obmcLogID,
68                      uint64_t timestamp, Entry::Level severity,
69                      const std::vector<std::string>& additionalData,
70                      const std::vector<std::string>& associations,
71                      const FFDCEntries& ffdc)
72 {
73     AdditionalData ad{additionalData};
74 
75     // If a PEL was passed in via a filename or in an ESEL,
76     // use that.  Otherwise, create one.
77     auto rawPelPath = ad.getValue(additional_data::rawPEL);
78     if (rawPelPath)
79     {
80         addRawPEL(*rawPelPath, obmcLogID);
81     }
82     else
83     {
84         auto esel = ad.getValue(additional_data::esel);
85         if (esel)
86         {
87             addESELPEL(*esel, obmcLogID);
88         }
89         else
90         {
91             createPEL(message, obmcLogID, timestamp, severity, additionalData,
92                       associations, ffdc);
93         }
94     }
95 
96     setEntryPath(obmcLogID);
97     setServiceProviderNotifyFlag(obmcLogID);
98 }
99 
100 void Manager::addRawPEL(const std::string& rawPelPath, uint32_t obmcLogID)
101 {
102     if (fs::exists(rawPelPath))
103     {
104         std::ifstream file(rawPelPath, std::ios::in | std::ios::binary);
105 
106         auto data = std::vector<uint8_t>(std::istreambuf_iterator<char>(file),
107                                          std::istreambuf_iterator<char>());
108         if (file.fail())
109         {
110             log<level::ERR>("Filesystem error reading a raw PEL",
111                             entry("PELFILE=%s", rawPelPath.c_str()),
112                             entry("OBMCLOGID=%d", obmcLogID));
113             // TODO, Decide what to do here. Maybe nothing.
114             return;
115         }
116 
117         file.close();
118 
119         addPEL(data, obmcLogID);
120 
121         std::error_code ec;
122         fs::remove(rawPelPath, ec);
123     }
124     else
125     {
126         log<level::ERR>("Raw PEL file from BMC event log does not exist",
127                         entry("PELFILE=%s", (rawPelPath).c_str()),
128                         entry("OBMCLOGID=%d", obmcLogID));
129     }
130 }
131 
132 void Manager::addPEL(std::vector<uint8_t>& pelData, uint32_t obmcLogID)
133 {
134     auto pel = std::make_unique<openpower::pels::PEL>(pelData, obmcLogID);
135     if (pel->valid())
136     {
137         // PELs created by others still need this field set by us.
138         pel->setCommitTime();
139 
140         // Assign Id other than to Hostbot PEL
141         if ((pel->privateHeader()).creatorID() !=
142             static_cast<uint8_t>(CreatorID::hostboot))
143         {
144             pel->assignID();
145         }
146         else
147         {
148             const Repository::LogID id{Repository::LogID::Pel(pel->id())};
149             auto result = _repo.hasPEL(id);
150             if (result)
151             {
152                 log<level::WARNING>(
153                     fmt::format("Duplicate HostBoot PEL Id {:#X} found; "
154                                 "moving it to archive folder",
155                                 pel->id())
156                         .c_str());
157 
158                 _repo.archivePEL(*pel);
159 
160                 // No need to keep around the openBMC event log entry
161                 scheduleObmcLogDelete(obmcLogID);
162                 return;
163             }
164         }
165 
166         // Update System Info to Extended User Data
167         pel->updateSysInfoInExtendedUserDataSection(*_dataIface);
168 
169         // Check for severity 0x51 and update boot progress SRC
170         updateProgressSRC(pel);
171 
172         try
173         {
174             log<level::DEBUG>(
175                 fmt::format("Adding external PEL {:#x} (BMC ID {}) to repo",
176                             pel->id(), obmcLogID)
177                     .c_str());
178 
179             _repo.add(pel);
180 
181             if (_repo.sizeWarning())
182             {
183                 scheduleRepoPrune();
184             }
185 
186             // Activate any resulting service indicators if necessary
187             auto policy = service_indicators::getPolicy(*_dataIface);
188             policy->activate(*pel);
189         }
190         catch (const std::exception& e)
191         {
192             // Probably a full or r/o filesystem, not much we can do.
193             log<level::ERR>("Unable to add PEL to Repository",
194                             entry("PEL_ID=0x%X", pel->id()));
195         }
196 
197         // Check if firmware should quiesce system due to error
198         checkPelAndQuiesce(pel);
199         updateEventId(pel);
200         updateResolution(pel);
201         createPELEntry(obmcLogID);
202     }
203     else
204     {
205         log<level::ERR>("Invalid PEL received from the host",
206                         entry("OBMCLOGID=%d", obmcLogID));
207 
208         AdditionalData ad;
209         ad.add("PLID", getNumberString("0x%08X", pel->plid()));
210         ad.add("OBMC_LOG_ID", std::to_string(obmcLogID));
211         ad.add("PEL_SIZE", std::to_string(pelData.size()));
212 
213         std::string asciiString;
214         auto src = pel->primarySRC();
215         if (src)
216         {
217             asciiString = (*src)->asciiString();
218         }
219 
220         ad.add("SRC", asciiString);
221 
222         _eventLogger.log("org.open_power.Logging.Error.BadHostPEL",
223                          Entry::Level::Error, ad);
224 
225         // Save it to a file for debug in the lab.  Just keep the latest.
226         // Not adding it to the PEL because it could already be max size
227         // and don't want to truncate an already invalid PEL.
228         std::ofstream pelFile{getPELRepoPath() / "badPEL"};
229         pelFile.write(reinterpret_cast<const char*>(pelData.data()),
230                       pelData.size());
231 
232         // No need to keep around the openBMC event log entry
233         scheduleObmcLogDelete(obmcLogID);
234     }
235 }
236 
237 void Manager::addESELPEL(const std::string& esel, uint32_t obmcLogID)
238 {
239     std::vector<uint8_t> data;
240 
241     log<level::DEBUG>("Adding PEL from ESEL",
242                       entry("OBMC_LOG_ID=%d", obmcLogID));
243 
244     try
245     {
246         data = std::move(eselToRawData(esel));
247     }
248     catch (const std::exception& e)
249     {
250         // Try to add it below anyway, so it follows the usual bad data path.
251         log<level::ERR>("Problems converting ESEL string to a byte vector");
252     }
253 
254     addPEL(data, obmcLogID);
255 }
256 
257 std::vector<uint8_t> Manager::eselToRawData(const std::string& esel)
258 {
259     std::vector<uint8_t> data;
260     std::string byteString;
261 
262     // As the eSEL string looks like: "50 48 00 ab ..." there are 3
263     // characters per raw byte, and since the actual PEL data starts
264     // at the 16th byte, the code will grab the PEL data starting at
265     // offset 48 in the string.
266     static constexpr size_t pelStart = 16 * 3;
267 
268     if (esel.size() <= pelStart)
269     {
270         log<level::ERR>("ESEL data too short",
271                         entry("ESEL_SIZE=%d", esel.size()));
272 
273         throw std::length_error("ESEL data too short");
274     }
275 
276     for (size_t i = pelStart; i < esel.size(); i += 3)
277     {
278         if (i + 1 < esel.size())
279         {
280             byteString = esel.substr(i, 2);
281             data.push_back(std::stoi(byteString, nullptr, 16));
282         }
283         else
284         {
285             log<level::ERR>("ESEL data too short",
286                             entry("ESEL_SIZE=%d", esel.size()));
287             throw std::length_error("ESEL data too short");
288         }
289     }
290 
291     return data;
292 }
293 
294 void Manager::erase(uint32_t obmcLogID)
295 {
296     Repository::LogID id{Repository::LogID::Obmc(obmcLogID)};
297 
298     auto path = std::string(OBJ_ENTRY) + '/' + std::to_string(obmcLogID);
299     _pelEntries.erase(path);
300     _repo.remove(id);
301 }
302 
303 bool Manager::isDeleteProhibited(uint32_t /*obmcLogID*/)
304 {
305     return false;
306 }
307 
308 PelFFDC Manager::convertToPelFFDC(const FFDCEntries& ffdc)
309 {
310     PelFFDC pelFFDC;
311 
312     std::for_each(ffdc.begin(), ffdc.end(), [&pelFFDC](const auto& f) {
313         PelFFDCfile pf;
314         pf.subType = std::get<ffdcSubtypePos>(f);
315         pf.version = std::get<ffdcVersionPos>(f);
316         pf.fd = std::get<ffdcFDPos>(f);
317 
318         switch (std::get<ffdcFormatPos>(f))
319         {
320             case Create::FFDCFormat::JSON:
321                 pf.format = UserDataFormat::json;
322                 break;
323             case Create::FFDCFormat::CBOR:
324                 pf.format = UserDataFormat::cbor;
325                 break;
326             case Create::FFDCFormat::Text:
327                 pf.format = UserDataFormat::text;
328                 break;
329             case Create::FFDCFormat::Custom:
330                 pf.format = UserDataFormat::custom;
331                 break;
332         }
333 
334         pelFFDC.push_back(pf);
335     });
336 
337     return pelFFDC;
338 }
339 
340 void Manager::createPEL(const std::string& message, uint32_t obmcLogID,
341                         uint64_t timestamp,
342                         phosphor::logging::Entry::Level severity,
343                         const std::vector<std::string>& additionalData,
344                         const std::vector<std::string>& /*associations*/,
345                         const FFDCEntries& ffdc)
346 {
347     auto entry = _registry.lookup(message, rg::LookupType::name);
348     auto pelFFDC = convertToPelFFDC(ffdc);
349     AdditionalData ad{additionalData};
350     std::string msg;
351 
352     if (!entry)
353     {
354         // Instead, get the default entry that means there is no
355         // other matching entry.  This error will still use the
356         // AdditionalData values of the original error, and this
357         // code will add the error message value that wasn't found
358         // to this AD.  This way, there will at least be a PEL,
359         // possibly with callouts, to allow users to debug the
360         // issue that caused the error even without its own PEL.
361         msg = "Event not found in PEL message registry: " + message;
362         log<level::INFO>(msg.c_str());
363 
364         entry = _registry.lookup(defaultLogMessage, rg::LookupType::name);
365         if (!entry)
366         {
367             log<level::ERR>("Default event not found in PEL message registry");
368             return;
369         }
370 
371         ad.add(additional_data::error, message);
372     }
373 
374     auto pel = std::make_unique<openpower::pels::PEL>(
375         *entry, obmcLogID, timestamp, severity, ad, pelFFDC, *_dataIface);
376 
377     _repo.add(pel);
378 
379     if (_repo.sizeWarning())
380     {
381         scheduleRepoPrune();
382     }
383 
384     auto src = pel->primarySRC();
385     if (src)
386     {
387         auto msg =
388             fmt::format("Created PEL {:#x} (BMC ID {}) with SRC {}", pel->id(),
389                         pel->obmcLogID(), (*src)->asciiString());
390         while (msg.back() == ' ')
391         {
392             msg.pop_back();
393         }
394         log<level::INFO>(msg.c_str());
395     }
396 
397     // Check for severity 0x51 and update boot progress SRC
398     updateProgressSRC(pel);
399 
400     // Activate any resulting service indicators if necessary
401     auto policy = service_indicators::getPolicy(*_dataIface);
402     policy->activate(*pel);
403 
404     // Check if firmware should quiesce system due to error
405     checkPelAndQuiesce(pel);
406     updateEventId(pel);
407     updateResolution(pel);
408     createPELEntry(obmcLogID);
409 }
410 
411 sdbusplus::message::unix_fd Manager::getPEL(uint32_t pelID)
412 {
413     Repository::LogID id{Repository::LogID::Pel(pelID)};
414     std::optional<int> fd;
415 
416     log<level::DEBUG>("getPEL", entry("PEL_ID=0x%X", pelID));
417 
418     try
419     {
420         fd = _repo.getPELFD(id);
421     }
422     catch (const std::exception& e)
423     {
424         throw common_error::InternalFailure();
425     }
426 
427     if (!fd)
428     {
429         throw common_error::InvalidArgument();
430     }
431 
432     scheduleFDClose(*fd);
433 
434     return *fd;
435 }
436 
437 void Manager::scheduleFDClose(int fd)
438 {
439     _fdCloserEventSource = std::make_unique<sdeventplus::source::Defer>(
440         _event, std::bind(std::mem_fn(&Manager::closeFD), this, fd,
441                           std::placeholders::_1));
442 }
443 
444 void Manager::closeFD(int fd, sdeventplus::source::EventBase& /*source*/)
445 {
446     close(fd);
447     _fdCloserEventSource.reset();
448 }
449 
450 std::vector<uint8_t> Manager::getPELFromOBMCID(uint32_t obmcLogID)
451 {
452     Repository::LogID id{Repository::LogID::Obmc(obmcLogID)};
453     std::optional<std::vector<uint8_t>> data;
454 
455     log<level::DEBUG>("getPELFromOBMCID", entry("OBMC_LOG_ID=%d", obmcLogID));
456 
457     try
458     {
459         data = _repo.getPELData(id);
460     }
461     catch (const std::exception& e)
462     {
463         throw common_error::InternalFailure();
464     }
465 
466     if (!data)
467     {
468         throw common_error::InvalidArgument();
469     }
470 
471     return *data;
472 }
473 
474 void Manager::hostAck(uint32_t pelID)
475 {
476     Repository::LogID id{Repository::LogID::Pel(pelID)};
477 
478     log<level::DEBUG>("HostAck", entry("PEL_ID=0x%X", pelID));
479 
480     if (!_repo.hasPEL(id))
481     {
482         throw common_error::InvalidArgument();
483     }
484 
485     if (_hostNotifier)
486     {
487         _hostNotifier->ackPEL(pelID);
488     }
489 }
490 
491 void Manager::hostReject(uint32_t pelID, RejectionReason reason)
492 {
493     Repository::LogID id{Repository::LogID::Pel(pelID)};
494 
495     log<level::DEBUG>("HostReject", entry("PEL_ID=0x%X", pelID),
496                       entry("REASON=%d", static_cast<int>(reason)));
497 
498     if (!_repo.hasPEL(id))
499     {
500         throw common_error::InvalidArgument();
501     }
502 
503     if (reason == RejectionReason::BadPEL)
504     {
505         AdditionalData data;
506         data.add("BAD_ID", getNumberString("0x%08X", pelID));
507         _eventLogger.log("org.open_power.Logging.Error.SentBadPELToHost",
508                          Entry::Level::Informational, data);
509         if (_hostNotifier)
510         {
511             _hostNotifier->setBadPEL(pelID);
512         }
513     }
514     else if ((reason == RejectionReason::HostFull) && _hostNotifier)
515     {
516         _hostNotifier->setHostFull(pelID);
517     }
518 }
519 
520 void Manager::scheduleRepoPrune()
521 {
522     _repoPrunerEventSource = std::make_unique<sdeventplus::source::Defer>(
523         _event, std::bind(std::mem_fn(&Manager::pruneRepo), this,
524                           std::placeholders::_1));
525 }
526 
527 void Manager::pruneRepo(sdeventplus::source::EventBase& /*source*/)
528 {
529     auto idsWithHwIsoEntry = _dataIface->getLogIDWithHwIsolation();
530 
531     auto idsToDelete = _repo.prune(idsWithHwIsoEntry);
532 
533     // Remove the OpenBMC event logs for the PELs that were just removed.
534     std::for_each(idsToDelete.begin(), idsToDelete.end(),
535                   [this](auto id) { this->_logManager.erase(id); });
536 
537     _repoPrunerEventSource.reset();
538 }
539 
540 void Manager::setupPELDeleteWatch()
541 {
542     _pelFileDeleteFD = inotify_init1(IN_NONBLOCK);
543     if (-1 == _pelFileDeleteFD)
544     {
545         auto e = errno;
546         std::string msg =
547             "inotify_init1 failed with errno " + std::to_string(e);
548         log<level::ERR>(msg.c_str());
549         abort();
550     }
551 
552     _pelFileDeleteWatchFD = inotify_add_watch(
553         _pelFileDeleteFD, _repo.repoPath().c_str(), IN_DELETE);
554     if (-1 == _pelFileDeleteWatchFD)
555     {
556         auto e = errno;
557         std::string msg =
558             "inotify_add_watch failed with error " + std::to_string(e);
559         log<level::ERR>(msg.c_str());
560         abort();
561     }
562 
563     _pelFileDeleteEventSource = std::make_unique<sdeventplus::source::IO>(
564         _event, _pelFileDeleteFD, EPOLLIN,
565         std::bind(std::mem_fn(&Manager::pelFileDeleted), this,
566                   std::placeholders::_1, std::placeholders::_2,
567                   std::placeholders::_3));
568 }
569 
570 void Manager::pelFileDeleted(sdeventplus::source::IO& /*io*/, int /*fd*/,
571                              uint32_t revents)
572 {
573     if (!(revents & EPOLLIN))
574     {
575         return;
576     }
577 
578     // An event for 1 PEL uses 48B. When all PELs are deleted at once,
579     // as many events as there is room for can be handled in one callback.
580     // A size of 2000 will allow 41 to be processed, with additional
581     // callbacks being needed to process the remaining ones.
582     std::array<uint8_t, 2000> data{};
583     auto bytesRead = read(_pelFileDeleteFD, data.data(), data.size());
584     if (bytesRead < 0)
585     {
586         auto e = errno;
587         std::string msg = "Failed reading data from inotify event, errno = " +
588                           std::to_string(e);
589         log<level::ERR>(msg.c_str());
590         abort();
591     }
592 
593     auto offset = 0;
594     while (offset < bytesRead)
595     {
596         auto event = reinterpret_cast<inotify_event*>(&data[offset]);
597         if (event->mask & IN_DELETE)
598         {
599             std::string filename{event->name};
600 
601             // Get the PEL ID from the filename and tell the
602             // repo it's been removed, and then delete the BMC
603             // event log if it's there.
604             auto pos = filename.find_first_of('_');
605             if (pos != std::string::npos)
606             {
607                 try
608                 {
609                     auto idString = filename.substr(pos + 1);
610                     auto pelID = std::stoul(idString, nullptr, 16);
611 
612                     Repository::LogID id{Repository::LogID::Pel(pelID)};
613                     auto removedLogID = _repo.remove(id);
614                     if (removedLogID)
615                     {
616                         _logManager.erase(removedLogID->obmcID.id);
617                     }
618                 }
619                 catch (const std::exception& e)
620                 {
621                     log<level::INFO>("Could not find PEL ID from its filename",
622                                      entry("FILENAME=%s", filename.c_str()));
623                 }
624             }
625         }
626 
627         offset += offsetof(inotify_event, name) + event->len;
628     }
629 }
630 
631 std::tuple<uint32_t, uint32_t> Manager::createPELWithFFDCFiles(
632     std::string message, Entry::Level severity,
633     std::map<std::string, std::string> additionalData,
634     std::vector<std::tuple<
635         sdbusplus::xyz::openbmc_project::Logging::server::Create::FFDCFormat,
636         uint8_t, uint8_t, sdbusplus::message::unix_fd>>
637         fFDC)
638 {
639     _logManager.createWithFFDC(message, severity, additionalData, fFDC);
640 
641     return {_logManager.lastEntryID(), _repo.lastPelID()};
642 }
643 
644 void Manager::checkPelAndQuiesce(std::unique_ptr<openpower::pels::PEL>& pel)
645 {
646     if ((pel->userHeader().severity() ==
647          static_cast<uint8_t>(SeverityType::nonError)) ||
648         (pel->userHeader().severity() ==
649          static_cast<uint8_t>(SeverityType::recovered)))
650     {
651         log<level::DEBUG>(
652             "PEL severity informational or recovered. no quiesce needed");
653         return;
654     }
655     if (!_logManager.isQuiesceOnErrorEnabled())
656     {
657         log<level::DEBUG>("QuiesceOnHwError not enabled, no quiesce needed");
658         return;
659     }
660 
661     CreatorID creatorID{pel->privateHeader().creatorID()};
662 
663     if ((creatorID != CreatorID::openBMC) &&
664         (creatorID != CreatorID::hostboot) &&
665         (creatorID != CreatorID::ioDrawer) && (creatorID != CreatorID::occ) &&
666         (creatorID != CreatorID::phyp))
667     {
668         return;
669     }
670 
671     // Now check if it has any type of callout
672     if (pel->isHwCalloutPresent())
673     {
674         log<level::INFO>(
675             "QuiesceOnHwError enabled, PEL severity not nonError or recovered, "
676             "and callout is present");
677 
678         _logManager.quiesceOnError(pel->obmcLogID());
679     }
680 }
681 
682 std::string Manager::getEventId(const openpower::pels::PEL& pel) const
683 {
684     std::string str;
685     auto src = pel.primarySRC();
686     if (src)
687     {
688         const auto& hexwords = (*src)->hexwordData();
689 
690         std::string refcode = (*src)->asciiString();
691         size_t pos = refcode.find_last_not_of(0x20);
692         if (pos != std::string::npos)
693         {
694             refcode.erase(pos + 1);
695         }
696         str = refcode;
697 
698         for (auto& value : hexwords)
699         {
700             str += " ";
701             str += getNumberString("%08X", value);
702         }
703     }
704     return str;
705 }
706 
707 void Manager::updateEventId(std::unique_ptr<openpower::pels::PEL>& pel)
708 {
709     std::string eventIdStr = getEventId(*pel);
710 
711     auto entryN = _logManager.entries.find(pel->obmcLogID());
712     if (entryN != _logManager.entries.end())
713     {
714         entryN->second->eventId(eventIdStr);
715     }
716 }
717 
718 std::string Manager::getResolution(const openpower::pels::PEL& pel) const
719 {
720     std::string str;
721     std::string resolution;
722     auto src = pel.primarySRC();
723     if (src)
724     {
725         // First extract the callout pointer and then go through
726         const auto& callouts = (*src)->callouts();
727         namespace pv = openpower::pels::pel_values;
728         // All PELs dont have callout, check before parsing callout data
729         if (callouts)
730         {
731             const auto& entries = callouts->callouts();
732             // Entry starts with index 1
733             uint8_t index = 1;
734             for (auto& entry : entries)
735             {
736                 resolution += std::to_string(index) + ". ";
737                 // Adding Location code to resolution
738                 if (!entry->locationCode().empty())
739                     resolution +=
740                         "Location Code: " + entry->locationCode() + ", ";
741                 if (entry->fruIdentity())
742                 {
743                     // Get priority and set the resolution string
744                     str = pv::getValue(entry->priority(),
745                                        pel_values::calloutPriorityValues,
746                                        pel_values::registryNamePos);
747                     str[0] = toupper(str[0]);
748                     resolution += "Priority: " + str + ", ";
749                     if (entry->fruIdentity()->getPN().has_value())
750                     {
751                         resolution +=
752                             "PN: " + entry->fruIdentity()->getPN().value() +
753                             ", ";
754                     }
755                     if (entry->fruIdentity()->getSN().has_value())
756                     {
757                         resolution +=
758                             "SN: " + entry->fruIdentity()->getSN().value() +
759                             ", ";
760                     }
761                     if (entry->fruIdentity()->getCCIN().has_value())
762                     {
763                         resolution +=
764                             "CCIN: " + entry->fruIdentity()->getCCIN().value() +
765                             ", ";
766                     }
767                     // Add the maintenance procedure
768                     if (entry->fruIdentity()->getMaintProc().has_value())
769                     {
770                         resolution +=
771                             "Procedure: " +
772                             entry->fruIdentity()->getMaintProc().value() + ", ";
773                     }
774                 }
775                 resolution.resize(resolution.size() - 2);
776                 resolution += "\n";
777                 index++;
778             }
779         }
780     }
781     return resolution;
782 }
783 
784 void Manager::updateResolution(std::unique_ptr<openpower::pels::PEL>& pel)
785 {
786     std::string callouts = getResolution(*pel);
787     auto entryN = _logManager.entries.find(pel->obmcLogID());
788     if (entryN != _logManager.entries.end())
789     {
790         entryN->second->resolution(callouts, true);
791     }
792 }
793 
794 void Manager::setEntryPath(uint32_t obmcLogID)
795 {
796     Repository::LogID id{Repository::LogID::Obmc(obmcLogID)};
797     if (auto attributes = _repo.getPELAttributes(id); attributes)
798     {
799         auto& attr = attributes.value().get();
800         auto entry = _logManager.entries.find(obmcLogID);
801         if (entry != _logManager.entries.end())
802         {
803             entry->second->path(attr.path, true);
804         }
805     }
806 }
807 
808 void Manager::setServiceProviderNotifyFlag(uint32_t obmcLogID)
809 {
810     Repository::LogID id{Repository::LogID::Obmc(obmcLogID)};
811     if (auto attributes = _repo.getPELAttributes(id); attributes)
812     {
813         auto& attr = attributes.value().get();
814         auto entry = _logManager.entries.find(obmcLogID);
815         if (entry != _logManager.entries.end())
816         {
817             entry->second->serviceProviderNotify(
818                 attr.actionFlags.test(callHomeFlagBit), true);
819         }
820     }
821 }
822 
823 void Manager::createPELEntry(uint32_t obmcLogID, bool skipIaSignal)
824 {
825     std::map<std::string, PropertiesVariant> varData;
826     Repository::LogID id{Repository::LogID::Obmc(obmcLogID)};
827     if (auto attributes = _repo.getPELAttributes(id); attributes)
828     {
829         namespace pv = openpower::pels::pel_values;
830         auto& attr = attributes.value().get();
831 
832         // get the hidden flag values
833         auto sevType = static_cast<SeverityType>(attr.severity & 0xF0);
834         auto isHidden = true;
835         if (((sevType != SeverityType::nonError) &&
836              attr.actionFlags.test(reportFlagBit) &&
837              !attr.actionFlags.test(hiddenFlagBit)) ||
838             ((sevType == SeverityType::nonError) &&
839              attr.actionFlags.test(serviceActionFlagBit)))
840         {
841             isHidden = false;
842         }
843         varData.emplace(std::string("Hidden"), isHidden);
844         varData.emplace(
845             std::string("Subsystem"),
846             pv::getValue(attr.subsystem, pel_values::subsystemValues));
847 
848         varData.emplace(
849             std::string("ManagementSystemAck"),
850             (attr.hmcState == TransmissionState::acked ? true : false));
851 
852         // Path to create PELEntry Interface is same as PEL
853         auto path = std::string(OBJ_ENTRY) + '/' + std::to_string(obmcLogID);
854         // Create Interface for PELEntry and set properties
855         auto pelEntry = std::make_unique<PELEntry>(_logManager.getBus(), path,
856                                                    varData, obmcLogID, &_repo);
857         if (!skipIaSignal)
858         {
859             pelEntry->emit_added();
860         }
861         _pelEntries.emplace(std::move(path), std::move(pelEntry));
862     }
863 }
864 
865 uint32_t Manager::getPELIdFromBMCLogId(uint32_t bmcLogId)
866 {
867     Repository::LogID id{Repository::LogID::Obmc(bmcLogId)};
868     if (auto logId = _repo.getLogID(id); !logId.has_value())
869     {
870         throw common_error::InvalidArgument();
871     }
872     else
873     {
874         return logId->pelID.id;
875     }
876 }
877 
878 uint32_t Manager::getBMCLogIdFromPELId(uint32_t pelId)
879 {
880     Repository::LogID id{Repository::LogID::Pel(pelId)};
881     if (auto logId = _repo.getLogID(id); !logId.has_value())
882     {
883         throw common_error::InvalidArgument();
884     }
885     else
886     {
887         return logId->obmcID.id;
888     }
889 }
890 
891 void Manager::updateProgressSRC(
892     std::unique_ptr<openpower::pels::PEL>& pel) const
893 {
894     // Check for pel severity of type - 0x51 = critical error, system
895     // termination
896     if (pel->userHeader().severity() == 0x51)
897     {
898         auto src = pel->primarySRC();
899         if (src)
900         {
901             std::vector<uint8_t> asciiSRC = (*src)->getSrcStruct();
902             uint64_t srcRefCode = 0;
903 
904             // Read bytes from offset [40-47] e.g. BD8D1001
905             for (int i = 0; i < 8; i++)
906             {
907                 srcRefCode |=
908                     (static_cast<uint64_t>(asciiSRC[40 + i]) << (8 * i));
909             }
910 
911             try
912             {
913                 _dataIface->createProgressSRC(srcRefCode, asciiSRC);
914             }
915             catch (std::exception& e)
916             {
917                 // Exception - may be no boot progress interface on dbus
918             }
919         }
920     }
921 }
922 
923 void Manager::scheduleObmcLogDelete(uint32_t obmcLogID)
924 {
925     _obmcLogDeleteEventSource = std::make_unique<sdeventplus::source::Defer>(
926         _event, std::bind(std::mem_fn(&Manager::deleteObmcLog), this,
927                           std::placeholders::_1, obmcLogID));
928 }
929 
930 void Manager::deleteObmcLog(sdeventplus::source::EventBase&, uint32_t obmcLogID)
931 {
932     log<level::INFO>(
933         fmt::format("Removing event log with no PEL: {}", obmcLogID).c_str());
934     _logManager.erase(obmcLogID);
935     _obmcLogDeleteEventSource.reset();
936 }
937 
938 } // namespace pels
939 } // namespace openpower
940