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