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