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