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 
381     _repo.add(pel);
382 
383     if (_repo.sizeWarning())
384     {
385         scheduleRepoPrune();
386     }
387 
388     auto src = pel->primarySRC();
389     if (src)
390     {
391         auto m = fmt::format("Created PEL {:#x} (BMC ID {}) with SRC {}",
392                              pel->id(), pel->obmcLogID(),
393                              (*src)->asciiString());
394         while (m.back() == ' ')
395         {
396             m.pop_back();
397         }
398         log<level::INFO>(m.c_str());
399     }
400 
401     // Check for severity 0x51 and update boot progress SRC
402     updateProgressSRC(pel);
403 
404     // Activate any resulting service indicators if necessary
405     auto policy = service_indicators::getPolicy(*_dataIface);
406     policy->activate(*pel);
407 
408     updateDBusSeverity(*pel);
409     updateEventId(pel);
410     updateResolution(*pel);
411     createPELEntry(obmcLogID);
412 
413     // Check if firmware should quiesce system due to error
414     checkPelAndQuiesce(pel);
415 }
416 
417 sdbusplus::message::unix_fd Manager::getPEL(uint32_t pelID)
418 {
419     Repository::LogID id{Repository::LogID::Pel(pelID)};
420     std::optional<int> fd;
421 
422     log<level::DEBUG>("getPEL", entry("PEL_ID=0x%X", pelID));
423 
424     try
425     {
426         fd = _repo.getPELFD(id);
427     }
428     catch (const std::exception& e)
429     {
430         throw common_error::InternalFailure();
431     }
432 
433     if (!fd)
434     {
435         throw common_error::InvalidArgument();
436     }
437 
438     scheduleFDClose(*fd);
439 
440     return *fd;
441 }
442 
443 void Manager::scheduleFDClose(int fd)
444 {
445     _fdCloserEventSource = std::make_unique<sdeventplus::source::Defer>(
446         _event, std::bind(std::mem_fn(&Manager::closeFD), this, fd,
447                           std::placeholders::_1));
448 }
449 
450 void Manager::closeFD(int fd, sdeventplus::source::EventBase& /*source*/)
451 {
452     close(fd);
453     _fdCloserEventSource.reset();
454 }
455 
456 std::vector<uint8_t> Manager::getPELFromOBMCID(uint32_t obmcLogID)
457 {
458     Repository::LogID id{Repository::LogID::Obmc(obmcLogID)};
459     std::optional<std::vector<uint8_t>> data;
460 
461     log<level::DEBUG>("getPELFromOBMCID", entry("OBMC_LOG_ID=%d", obmcLogID));
462 
463     try
464     {
465         data = _repo.getPELData(id);
466     }
467     catch (const std::exception& e)
468     {
469         throw common_error::InternalFailure();
470     }
471 
472     if (!data)
473     {
474         throw common_error::InvalidArgument();
475     }
476 
477     return *data;
478 }
479 
480 void Manager::hostAck(uint32_t pelID)
481 {
482     Repository::LogID id{Repository::LogID::Pel(pelID)};
483 
484     log<level::DEBUG>("HostAck", entry("PEL_ID=0x%X", pelID));
485 
486     if (!_repo.hasPEL(id))
487     {
488         throw common_error::InvalidArgument();
489     }
490 
491     if (_hostNotifier)
492     {
493         _hostNotifier->ackPEL(pelID);
494     }
495 }
496 
497 void Manager::hostReject(uint32_t pelID, RejectionReason reason)
498 {
499     Repository::LogID id{Repository::LogID::Pel(pelID)};
500 
501     log<level::DEBUG>("HostReject", entry("PEL_ID=0x%X", pelID),
502                       entry("REASON=%d", static_cast<int>(reason)));
503 
504     if (!_repo.hasPEL(id))
505     {
506         throw common_error::InvalidArgument();
507     }
508 
509     if (reason == RejectionReason::BadPEL)
510     {
511         AdditionalData data;
512         data.add("BAD_ID", getNumberString("0x%08X", pelID));
513         _eventLogger.log("org.open_power.Logging.Error.SentBadPELToHost",
514                          Entry::Level::Informational, data);
515         if (_hostNotifier)
516         {
517             _hostNotifier->setBadPEL(pelID);
518         }
519     }
520     else if ((reason == RejectionReason::HostFull) && _hostNotifier)
521     {
522         _hostNotifier->setHostFull(pelID);
523     }
524 }
525 
526 void Manager::scheduleRepoPrune()
527 {
528     _repoPrunerEventSource = std::make_unique<sdeventplus::source::Defer>(
529         _event, std::bind(std::mem_fn(&Manager::pruneRepo), this,
530                           std::placeholders::_1));
531 }
532 
533 void Manager::pruneRepo(sdeventplus::source::EventBase& /*source*/)
534 {
535     auto idsWithHwIsoEntry = _dataIface->getLogIDWithHwIsolation();
536 
537     auto idsToDelete = _repo.prune(idsWithHwIsoEntry);
538 
539     // Remove the OpenBMC event logs for the PELs that were just removed.
540     std::for_each(idsToDelete.begin(), idsToDelete.end(),
541                   [this](auto id) { this->_logManager.erase(id); });
542 
543     _repoPrunerEventSource.reset();
544 }
545 
546 void Manager::setupPELDeleteWatch()
547 {
548     _pelFileDeleteFD = inotify_init1(IN_NONBLOCK);
549     if (-1 == _pelFileDeleteFD)
550     {
551         auto e = errno;
552         std::string msg = "inotify_init1 failed with errno " +
553                           std::to_string(e);
554         log<level::ERR>(msg.c_str());
555         abort();
556     }
557 
558     _pelFileDeleteWatchFD = inotify_add_watch(
559         _pelFileDeleteFD, _repo.repoPath().c_str(), IN_DELETE);
560     if (-1 == _pelFileDeleteWatchFD)
561     {
562         auto e = errno;
563         std::string msg = "inotify_add_watch failed with error " +
564                           std::to_string(e);
565         log<level::ERR>(msg.c_str());
566         abort();
567     }
568 
569     _pelFileDeleteEventSource = std::make_unique<sdeventplus::source::IO>(
570         _event, _pelFileDeleteFD, EPOLLIN,
571         std::bind(std::mem_fn(&Manager::pelFileDeleted), this,
572                   std::placeholders::_1, std::placeholders::_2,
573                   std::placeholders::_3));
574 }
575 
576 void Manager::pelFileDeleted(sdeventplus::source::IO& /*io*/, int /*fd*/,
577                              uint32_t revents)
578 {
579     if (!(revents & EPOLLIN))
580     {
581         return;
582     }
583 
584     // An event for 1 PEL uses 48B. When all PELs are deleted at once,
585     // as many events as there is room for can be handled in one callback.
586     // A size of 2000 will allow 41 to be processed, with additional
587     // callbacks being needed to process the remaining ones.
588     std::array<uint8_t, 2000> data{};
589     auto bytesRead = read(_pelFileDeleteFD, data.data(), data.size());
590     if (bytesRead < 0)
591     {
592         auto e = errno;
593         std::string msg = "Failed reading data from inotify event, errno = " +
594                           std::to_string(e);
595         log<level::ERR>(msg.c_str());
596         abort();
597     }
598 
599     auto offset = 0;
600     while (offset < bytesRead)
601     {
602         auto event = reinterpret_cast<inotify_event*>(&data[offset]);
603         if (event->mask & IN_DELETE)
604         {
605             std::string filename{event->name};
606 
607             // Get the PEL ID from the filename and tell the
608             // repo it's been removed, and then delete the BMC
609             // event log if it's there.
610             auto pos = filename.find_first_of('_');
611             if (pos != std::string::npos)
612             {
613                 try
614                 {
615                     auto idString = filename.substr(pos + 1);
616                     auto pelID = std::stoul(idString, nullptr, 16);
617 
618                     Repository::LogID id{Repository::LogID::Pel(pelID)};
619                     auto removedLogID = _repo.remove(id);
620                     if (removedLogID)
621                     {
622                         _logManager.erase(removedLogID->obmcID.id);
623                     }
624                 }
625                 catch (const std::exception& e)
626                 {
627                     log<level::INFO>("Could not find PEL ID from its filename",
628                                      entry("FILENAME=%s", filename.c_str()));
629                 }
630             }
631         }
632 
633         offset += offsetof(inotify_event, name) + event->len;
634     }
635 }
636 
637 std::tuple<uint32_t, uint32_t> Manager::createPELWithFFDCFiles(
638     std::string message, Entry::Level severity,
639     std::map<std::string, std::string> additionalData,
640     std::vector<std::tuple<
641         sdbusplus::xyz::openbmc_project::Logging::server::Create::FFDCFormat,
642         uint8_t, uint8_t, sdbusplus::message::unix_fd>>
643         fFDC)
644 {
645     _logManager.createWithFFDC(message, severity, additionalData, fFDC);
646 
647     return {_logManager.lastEntryID(), _repo.lastPelID()};
648 }
649 
650 std::string Manager::getPELJSON(uint32_t obmcLogID)
651 {
652     // Throws InvalidArgument if not found
653     auto pelID = getPELIdFromBMCLogId(obmcLogID);
654 
655     auto cmd = fmt::format("/usr/bin/peltool -i {:#x}", pelID);
656 
657     FILE* pipe = popen(cmd.c_str(), "r");
658     if (!pipe)
659     {
660         log<level::ERR>(fmt::format("Error running {}", cmd).c_str());
661         throw common_error::InternalFailure();
662     }
663 
664     std::string output;
665     std::array<char, 1024> buffer;
666     while (fgets(buffer.data(), buffer.size(), pipe) != nullptr)
667     {
668         output.append(buffer.data());
669     }
670 
671     int rc = pclose(pipe);
672     if (WEXITSTATUS(rc) != 0)
673     {
674         log<level::ERR>(
675             fmt::format("Error running {}, rc = {}", cmd, rc).c_str());
676         throw common_error::InternalFailure();
677     }
678 
679     return output;
680 }
681 
682 void Manager::checkPelAndQuiesce(std::unique_ptr<openpower::pels::PEL>& pel)
683 {
684     if ((pel->userHeader().severity() ==
685          static_cast<uint8_t>(SeverityType::nonError)) ||
686         (pel->userHeader().severity() ==
687          static_cast<uint8_t>(SeverityType::recovered)))
688     {
689         log<level::DEBUG>(
690             "PEL severity informational or recovered. no quiesce needed");
691         return;
692     }
693     if (!_logManager.isQuiesceOnErrorEnabled())
694     {
695         log<level::DEBUG>("QuiesceOnHwError not enabled, no quiesce needed");
696         return;
697     }
698 
699     CreatorID creatorID{pel->privateHeader().creatorID()};
700 
701     if ((creatorID != CreatorID::openBMC) &&
702         (creatorID != CreatorID::hostboot) &&
703         (creatorID != CreatorID::ioDrawer) && (creatorID != CreatorID::occ) &&
704         (creatorID != CreatorID::phyp))
705     {
706         return;
707     }
708 
709     // Now check if it has any type of callout
710     if (pel->isHwCalloutPresent())
711     {
712         log<level::INFO>(
713             "QuiesceOnHwError enabled, PEL severity not nonError or recovered, "
714             "and callout is present");
715 
716         _logManager.quiesceOnError(pel->obmcLogID());
717     }
718 }
719 
720 std::string Manager::getEventId(const openpower::pels::PEL& pel) const
721 {
722     std::string str;
723     auto src = pel.primarySRC();
724     if (src)
725     {
726         const auto& hexwords = (*src)->hexwordData();
727 
728         std::string refcode = (*src)->asciiString();
729         size_t pos = refcode.find_last_not_of(0x20);
730         if (pos != std::string::npos)
731         {
732             refcode.erase(pos + 1);
733         }
734         str = refcode;
735 
736         for (auto& value : hexwords)
737         {
738             str += " ";
739             str += getNumberString("%08X", value);
740         }
741     }
742     return sanitizeFieldForDBus(str);
743 }
744 
745 void Manager::updateEventId(std::unique_ptr<openpower::pels::PEL>& pel)
746 {
747     std::string eventIdStr = getEventId(*pel);
748 
749     auto entryN = _logManager.entries.find(pel->obmcLogID());
750     if (entryN != _logManager.entries.end())
751     {
752         entryN->second->eventId(eventIdStr);
753     }
754 }
755 
756 std::string Manager::sanitizeFieldForDBus(std::string field)
757 {
758     std::for_each(field.begin(), field.end(), [](char& ch) {
759         if (((ch < ' ') || (ch > '~')) && (ch != '\n') && (ch != '\t'))
760         {
761             ch = ' ';
762         }
763     });
764     return field;
765 }
766 
767 std::string Manager::getResolution(const openpower::pels::PEL& pel) const
768 {
769     std::string str;
770     std::string resolution;
771     auto src = pel.primarySRC();
772     if (src)
773     {
774         // First extract the callout pointer and then go through
775         const auto& callouts = (*src)->callouts();
776         namespace pv = openpower::pels::pel_values;
777         // All PELs dont have callout, check before parsing callout data
778         if (callouts)
779         {
780             const auto& entries = callouts->callouts();
781             // Entry starts with index 1
782             uint8_t index = 1;
783             for (auto& entry : entries)
784             {
785                 resolution += std::to_string(index) + ". ";
786                 // Adding Location code to resolution
787                 if (!entry->locationCode().empty())
788                     resolution += "Location Code: " + entry->locationCode() +
789                                   ", ";
790                 if (entry->fruIdentity())
791                 {
792                     // Get priority and set the resolution string
793                     str = pv::getValue(entry->priority(),
794                                        pel_values::calloutPriorityValues,
795                                        pel_values::registryNamePos);
796                     str[0] = toupper(str[0]);
797                     resolution += "Priority: " + str + ", ";
798                     if (entry->fruIdentity()->getPN().has_value())
799                     {
800                         resolution +=
801                             "PN: " + entry->fruIdentity()->getPN().value() +
802                             ", ";
803                     }
804                     if (entry->fruIdentity()->getSN().has_value())
805                     {
806                         resolution +=
807                             "SN: " + entry->fruIdentity()->getSN().value() +
808                             ", ";
809                     }
810                     if (entry->fruIdentity()->getCCIN().has_value())
811                     {
812                         resolution +=
813                             "CCIN: " + entry->fruIdentity()->getCCIN().value() +
814                             ", ";
815                     }
816                     // Add the maintenance procedure
817                     if (entry->fruIdentity()->getMaintProc().has_value())
818                     {
819                         resolution +=
820                             "Procedure: " +
821                             entry->fruIdentity()->getMaintProc().value() + ", ";
822                     }
823                 }
824                 resolution.resize(resolution.size() - 2);
825                 resolution += "\n";
826                 index++;
827             }
828         }
829     }
830     return sanitizeFieldForDBus(resolution);
831 }
832 
833 bool Manager::updateResolution(const openpower::pels::PEL& pel)
834 {
835     std::string callouts = getResolution(pel);
836     auto entryN = _logManager.entries.find(pel.obmcLogID());
837     if (entryN != _logManager.entries.end())
838     {
839         entryN->second->resolution(callouts, true);
840     }
841 
842     return false;
843 }
844 
845 void Manager::updateDBusSeverity(const openpower::pels::PEL& pel)
846 {
847     // The final severity of the PEL may not agree with the
848     // original severity of the D-Bus event log.  Update the
849     // D-Bus property to match in some cases.  This is to
850     // ensure there isn't a Critical or Warning Redfish event
851     // log for an informational or recovered PEL (or vice versa).
852     // This doesn't make an explicit call to serialize the new
853     // event log property value because updateEventId() is called
854     // right after this and will do it.
855     auto sevType =
856         static_cast<SeverityType>(pel.userHeader().severity() & 0xF0);
857 
858     auto entryN = _logManager.entries.find(pel.obmcLogID());
859     if (entryN != _logManager.entries.end())
860     {
861         auto newSeverity = fixupLogSeverity(entryN->second->severity(),
862                                             sevType);
863         if (newSeverity)
864         {
865             log<level::INFO>(
866                 fmt::format(
867                     "Changing event log {} severity from {} "
868                     "to {} to match PEL",
869                     entryN->second->id(),
870                     Entry::convertLevelToString(entryN->second->severity()),
871                     Entry::convertLevelToString(*newSeverity))
872                     .c_str());
873 
874             entryN->second->severity(*newSeverity, true);
875         }
876     }
877 }
878 
879 void Manager::setEntryPath(uint32_t obmcLogID)
880 {
881     Repository::LogID id{Repository::LogID::Obmc(obmcLogID)};
882     if (auto attributes = _repo.getPELAttributes(id); attributes)
883     {
884         auto& attr = attributes.value().get();
885         auto entry = _logManager.entries.find(obmcLogID);
886         if (entry != _logManager.entries.end())
887         {
888             entry->second->path(attr.path, true);
889         }
890     }
891 }
892 
893 void Manager::setServiceProviderNotifyFlag(uint32_t obmcLogID)
894 {
895     Repository::LogID id{Repository::LogID::Obmc(obmcLogID)};
896     if (auto attributes = _repo.getPELAttributes(id); attributes)
897     {
898         auto& attr = attributes.value().get();
899         auto entry = _logManager.entries.find(obmcLogID);
900         if (entry != _logManager.entries.end())
901         {
902             if (attr.actionFlags.test(callHomeFlagBit))
903             {
904                 entry->second->serviceProviderNotify(Entry::Notify::Notify);
905             }
906             else
907             {
908                 entry->second->serviceProviderNotify(Entry::Notify::Inhibit);
909             }
910         }
911     }
912 }
913 
914 void Manager::createPELEntry(uint32_t obmcLogID, bool skipIaSignal)
915 {
916     std::map<std::string, PropertiesVariant> varData;
917     Repository::LogID id{Repository::LogID::Obmc(obmcLogID)};
918     if (auto attributes = _repo.getPELAttributes(id); attributes)
919     {
920         namespace pv = openpower::pels::pel_values;
921         auto& attr = attributes.value().get();
922 
923         // get the hidden flag values
924         auto sevType = static_cast<SeverityType>(attr.severity & 0xF0);
925         auto isHidden = true;
926         if (((sevType != SeverityType::nonError) &&
927              attr.actionFlags.test(reportFlagBit) &&
928              !attr.actionFlags.test(hiddenFlagBit)) ||
929             ((sevType == SeverityType::nonError) &&
930              attr.actionFlags.test(serviceActionFlagBit)))
931         {
932             isHidden = false;
933         }
934         varData.emplace(std::string("Hidden"), isHidden);
935         varData.emplace(
936             std::string("Subsystem"),
937             pv::getValue(attr.subsystem, pel_values::subsystemValues));
938 
939         varData.emplace(
940             std::string("ManagementSystemAck"),
941             (attr.hmcState == TransmissionState::acked ? true : false));
942 
943         // Path to create PELEntry Interface is same as PEL
944         auto path = std::string(OBJ_ENTRY) + '/' + std::to_string(obmcLogID);
945         // Create Interface for PELEntry and set properties
946         auto pelEntry = std::make_unique<PELEntry>(_logManager.getBus(), path,
947                                                    varData, obmcLogID, &_repo);
948         if (!skipIaSignal)
949         {
950             pelEntry->emit_added();
951         }
952         _pelEntries.emplace(std::move(path), std::move(pelEntry));
953     }
954 }
955 
956 uint32_t Manager::getPELIdFromBMCLogId(uint32_t bmcLogId)
957 {
958     Repository::LogID id{Repository::LogID::Obmc(bmcLogId)};
959     if (auto logId = _repo.getLogID(id); !logId.has_value())
960     {
961         throw common_error::InvalidArgument();
962     }
963     else
964     {
965         return logId->pelID.id;
966     }
967 }
968 
969 uint32_t Manager::getBMCLogIdFromPELId(uint32_t pelId)
970 {
971     Repository::LogID id{Repository::LogID::Pel(pelId)};
972     if (auto logId = _repo.getLogID(id); !logId.has_value())
973     {
974         throw common_error::InvalidArgument();
975     }
976     else
977     {
978         return logId->obmcID.id;
979     }
980 }
981 
982 void Manager::updateProgressSRC(
983     std::unique_ptr<openpower::pels::PEL>& pel) const
984 {
985     // Check for pel severity of type - 0x51 = critical error, system
986     // termination
987     if (pel->userHeader().severity() == 0x51)
988     {
989         auto src = pel->primarySRC();
990         if (src)
991         {
992             std::vector<uint8_t> asciiSRC = (*src)->getSrcStruct();
993             uint64_t srcRefCode = 0;
994 
995             // Read bytes from offset [40-47] e.g. BD8D1001
996             for (int i = 0; i < 8; i++)
997             {
998                 srcRefCode |= (static_cast<uint64_t>(asciiSRC[40 + i])
999                                << (8 * i));
1000             }
1001 
1002             try
1003             {
1004                 _dataIface->createProgressSRC(srcRefCode, asciiSRC);
1005             }
1006             catch (std::exception& e)
1007             {
1008                 // Exception - may be no boot progress interface on dbus
1009             }
1010         }
1011     }
1012 }
1013 
1014 void Manager::scheduleObmcLogDelete(uint32_t obmcLogID)
1015 {
1016     _obmcLogDeleteEventSource = std::make_unique<sdeventplus::source::Defer>(
1017         _event, std::bind(std::mem_fn(&Manager::deleteObmcLog), this,
1018                           std::placeholders::_1, obmcLogID));
1019 }
1020 
1021 void Manager::deleteObmcLog(sdeventplus::source::EventBase&, uint32_t obmcLogID)
1022 {
1023     log<level::INFO>(
1024         fmt::format("Removing event log with no PEL: {}", obmcLogID).c_str());
1025     _logManager.erase(obmcLogID);
1026     _obmcLogDeleteEventSource.reset();
1027 }
1028 
1029 } // namespace pels
1030 } // namespace openpower
1031