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 "extensions/openpower-pels/manager.hpp"
17 #include "log_manager.hpp"
18 #include "mocks.hpp"
19 #include "pel_utils.hpp"
20 
21 #include <sdbusplus/test/sdbus_mock.hpp>
22 #include <xyz/openbmc_project/Common/error.hpp>
23 
24 #include <fstream>
25 #include <regex>
26 
27 #include <gtest/gtest.h>
28 
29 using namespace openpower::pels;
30 namespace fs = std::filesystem;
31 
32 using ::testing::NiceMock;
33 using ::testing::Return;
34 using json = nlohmann::json;
35 
36 class TestLogger
37 {
38   public:
39     void log(const std::string& name, phosphor::logging::Entry::Level level,
40              const EventLogger::ADMap& additionalData)
41     {
42         errName = name;
43         errLevel = level;
44         ad = additionalData;
45     }
46 
47     std::string errName;
48     phosphor::logging::Entry::Level errLevel;
49     EventLogger::ADMap ad;
50 };
51 
52 class ManagerTest : public CleanPELFiles
53 {
54   public:
55     ManagerTest() :
56         bus(sdbusplus::get_mocked_new(&sdbusInterface)),
57         logManager(bus, "logging_path")
58     {
59         sd_event_default(&sdEvent);
60     }
61 
62     fs::path makeTempDir()
63     {
64         char path[] = "/tmp/tempnameXXXXXX";
65         std::filesystem::path dir = mkdtemp(path);
66         dirsToRemove.push_back(dir);
67         return dir;
68     }
69 
70     ~ManagerTest()
71     {
72         for (const auto& d : dirsToRemove)
73         {
74             std::filesystem::remove_all(d);
75         }
76         sd_event_unref(sdEvent);
77     }
78 
79     NiceMock<sdbusplus::SdBusMock> sdbusInterface;
80     sdbusplus::bus_t bus;
81     phosphor::logging::internal::Manager logManager;
82     sd_event* sdEvent;
83     TestLogger logger;
84     std::vector<std::filesystem::path> dirsToRemove;
85 };
86 
87 std::optional<fs::path> findAnyPELInRepo()
88 {
89     // PELs are named <timestamp>_<ID>
90     std::regex expr{"\\d+_\\d+"};
91 
92     for (auto& f : fs::directory_iterator(getPELRepoPath() / "logs"))
93     {
94         if (std::regex_search(f.path().string(), expr))
95         {
96             return f.path();
97         }
98     }
99     return std::nullopt;
100 }
101 
102 size_t countPELsInRepo()
103 {
104     size_t count = 0;
105     std::regex expr{"\\d+_\\d+"};
106 
107     for (auto& f : fs::directory_iterator(getPELRepoPath() / "logs"))
108     {
109         if (std::regex_search(f.path().string(), expr))
110         {
111             count++;
112         }
113     }
114     return count;
115 }
116 
117 void deletePELFile(uint32_t id)
118 {
119     char search[20];
120 
121     sprintf(search, "\\d+_%.8X", id);
122     std::regex expr{search};
123 
124     for (auto& f : fs::directory_iterator(getPELRepoPath() / "logs"))
125     {
126         if (std::regex_search(f.path().string(), expr))
127         {
128             fs::remove(f.path());
129             break;
130         }
131     }
132 }
133 
134 // Test that using the RAWPEL=<file> with the Manager::create() call gets
135 // a PEL saved in the repository.
136 TEST_F(ManagerTest, TestCreateWithPEL)
137 {
138     std::unique_ptr<DataInterfaceBase> dataIface =
139         std::make_unique<MockDataInterface>();
140 
141     std::unique_ptr<JournalBase> journal = std::make_unique<MockJournal>();
142 
143     openpower::pels::Manager manager{
144         logManager, std::move(dataIface),
145         std::bind(std::mem_fn(&TestLogger::log), &logger, std::placeholders::_1,
146                   std::placeholders::_2, std::placeholders::_3),
147         std::move(journal)};
148 
149     // Create a PEL, write it to a file, and pass that filename into
150     // the create function.
151     auto data = pelDataFactory(TestPELType::pelSimple);
152 
153     fs::path pelFilename = makeTempDir() / "rawpel";
154     std::ofstream pelFile{pelFilename};
155     pelFile.write(reinterpret_cast<const char*>(data.data()), data.size());
156     pelFile.close();
157 
158     std::string adItem = "RAWPEL=" + pelFilename.string();
159     std::vector<std::string> additionalData{adItem};
160     std::vector<std::string> associations;
161 
162     manager.create("error message", 42, 0,
163                    phosphor::logging::Entry::Level::Error, additionalData,
164                    associations);
165 
166     // Find the file in the PEL repository directory
167     auto pelPathInRepo = findAnyPELInRepo();
168 
169     EXPECT_TRUE(pelPathInRepo);
170 
171     // Now remove it based on its OpenBMC event log ID
172     manager.erase(42);
173 
174     pelPathInRepo = findAnyPELInRepo();
175 
176     EXPECT_FALSE(pelPathInRepo);
177 }
178 
179 TEST_F(ManagerTest, TestCreateWithInvalidPEL)
180 {
181     std::unique_ptr<DataInterfaceBase> dataIface =
182         std::make_unique<MockDataInterface>();
183 
184     std::unique_ptr<JournalBase> journal = std::make_unique<MockJournal>();
185 
186     openpower::pels::Manager manager{
187         logManager, std::move(dataIface),
188         std::bind(std::mem_fn(&TestLogger::log), &logger, std::placeholders::_1,
189                   std::placeholders::_2, std::placeholders::_3),
190         std::move(journal)};
191 
192     // Create a PEL, write it to a file, and pass that filename into
193     // the create function.
194     auto data = pelDataFactory(TestPELType::pelSimple);
195 
196     // Truncate it to make it invalid.
197     data.resize(200);
198 
199     fs::path pelFilename = makeTempDir() / "rawpel";
200     std::ofstream pelFile{pelFilename};
201     pelFile.write(reinterpret_cast<const char*>(data.data()), data.size());
202     pelFile.close();
203 
204     std::string adItem = "RAWPEL=" + pelFilename.string();
205     std::vector<std::string> additionalData{adItem};
206     std::vector<std::string> associations;
207 
208     manager.create("error message", 42, 0,
209                    phosphor::logging::Entry::Level::Error, additionalData,
210                    associations);
211 
212     // Run the event loop to log the bad PEL event
213     sdeventplus::Event e{sdEvent};
214     e.run(std::chrono::milliseconds(1));
215 
216     PEL invalidPEL{data};
217     EXPECT_EQ(logger.errName, "org.open_power.Logging.Error.BadHostPEL");
218     EXPECT_EQ(logger.errLevel, phosphor::logging::Entry::Level::Error);
219     EXPECT_EQ(std::stoi(logger.ad["PLID"], nullptr, 16), invalidPEL.plid());
220     EXPECT_EQ(logger.ad["OBMC_LOG_ID"], "42");
221     EXPECT_EQ(logger.ad["SRC"], (*invalidPEL.primarySRC())->asciiString());
222     EXPECT_EQ(logger.ad["PEL_SIZE"], std::to_string(data.size()));
223 
224     // Check that the bad PEL data was saved to a file.
225     auto badPELData = readPELFile(getPELRepoPath() / "badPEL");
226     EXPECT_EQ(*badPELData, data);
227 }
228 
229 // Test that the message registry can be used to build a PEL.
230 TEST_F(ManagerTest, TestCreateWithMessageRegistry)
231 {
232     const auto registry = R"(
233 {
234     "PELs":
235     [
236         {
237             "Name": "xyz.openbmc_project.Error.Test",
238             "Subsystem": "power_supply",
239             "ActionFlags": ["service_action", "report"],
240             "SRC":
241             {
242                 "ReasonCode": "0x2030"
243             },
244             "Callouts": [
245                 {
246                     "CalloutList": [
247                         {"Priority": "high", "Procedure": "BMC0001"},
248                         {"Priority": "medium", "SymbolicFRU": "service_docs"}
249                     ]
250                 }
251             ],
252             "Documentation":
253             {
254                 "Description": "A PGOOD Fault",
255                 "Message": "PS had a PGOOD Fault"
256             }
257         },
258         {
259             "Name": "xyz.openbmc_project.Logging.Error.Default",
260             "Subsystem": "bmc_firmware",
261             "SRC":
262             {
263                 "ReasonCode": "0x2031"
264             },
265             "Documentation":
266             {
267                 "Description": "The entry used when no match found",
268                 "Message": "This is a generic SRC"
269             }
270         }
271     ]
272 }
273 )";
274 
275     auto path = getPELReadOnlyDataPath();
276     fs::create_directories(path);
277     path /= "message_registry.json";
278 
279     std::ofstream registryFile{path};
280     registryFile << registry;
281     registryFile.close();
282 
283     std::unique_ptr<DataInterfaceBase> dataIface =
284         std::make_unique<MockDataInterface>();
285 
286     std::unique_ptr<JournalBase> journal = std::make_unique<MockJournal>();
287 
288     openpower::pels::Manager manager{
289         logManager, std::move(dataIface),
290         std::bind(std::mem_fn(&TestLogger::log), &logger, std::placeholders::_1,
291                   std::placeholders::_2, std::placeholders::_3),
292         std::move(journal)};
293 
294     std::vector<std::string> additionalData{"FOO=BAR"};
295     std::vector<std::string> associations;
296 
297     // Create the event log to create the PEL from.
298     manager.create("xyz.openbmc_project.Error.Test", 33, 0,
299                    phosphor::logging::Entry::Level::Error, additionalData,
300                    associations);
301 
302     // Ensure a PEL was created in the repository
303     auto pelFile = findAnyPELInRepo();
304     ASSERT_TRUE(pelFile);
305 
306     auto data = readPELFile(*pelFile);
307     PEL pel(*data);
308 
309     // Spot check it.  Other testcases cover the details.
310     EXPECT_TRUE(pel.valid());
311     EXPECT_EQ(pel.obmcLogID(), 33);
312     EXPECT_EQ(pel.primarySRC().value()->asciiString(),
313               "BD612030                        ");
314     // Check if the eventId creation is good
315     EXPECT_EQ(manager.getEventId(pel),
316               "BD612030 00000055 00000010 00000000 00000000 00000000 00000000 "
317               "00000000 00000000");
318     // Check if resolution property creation is good
319     EXPECT_EQ(manager.getResolution(pel),
320               "1. Priority: High, Procedure: BMC0001\n2. Priority: Medium, PN: "
321               "SVCDOCS\n");
322 
323     // Remove it
324     manager.erase(33);
325     pelFile = findAnyPELInRepo();
326     EXPECT_FALSE(pelFile);
327 
328     // Create an event log that can't be found in the registry.
329     // In this case, xyz.openbmc_project.Logging.Error.Default will
330     // be used as the key instead to find a registry match.
331     manager.create("xyz.openbmc_project.Error.Foo", 42, 0,
332                    phosphor::logging::Entry::Level::Error, additionalData,
333                    associations);
334 
335     // Ensure a PEL was still created in the repository
336     pelFile = findAnyPELInRepo();
337     ASSERT_TRUE(pelFile);
338 
339     data = readPELFile(*pelFile);
340     PEL newPEL(*data);
341 
342     EXPECT_TRUE(newPEL.valid());
343     EXPECT_EQ(newPEL.obmcLogID(), 42);
344     EXPECT_EQ(newPEL.primarySRC().value()->asciiString(),
345               "BD8D2031                        ");
346 
347     // Check for both the original AdditionalData item as well as
348     // the ERROR_NAME item that should contain the error message
349     // property that wasn't found.
350     std::string errorName;
351     std::string adItem;
352 
353     for (const auto& section : newPEL.optionalSections())
354     {
355         if (SectionID::userData == static_cast<SectionID>(section->header().id))
356         {
357             if (UserDataFormat::json ==
358                 static_cast<UserDataFormat>(section->header().subType))
359             {
360                 auto ud = static_cast<UserData*>(section.get());
361 
362                 // Check that there was a UserData section added that
363                 // contains debug details about the device.
364                 const auto& d = ud->data();
365                 std::string jsonString{d.begin(), d.end()};
366                 auto json = nlohmann::json::parse(jsonString);
367 
368                 if (json.contains("ERROR_NAME"))
369                 {
370                     errorName = json["ERROR_NAME"].get<std::string>();
371                 }
372 
373                 if (json.contains("FOO"))
374                 {
375                     adItem = json["FOO"].get<std::string>();
376                 }
377             }
378         }
379         if (!errorName.empty())
380         {
381             break;
382         }
383     }
384 
385     EXPECT_EQ(errorName, "xyz.openbmc_project.Error.Foo");
386     EXPECT_EQ(adItem, "BAR");
387 }
388 
389 TEST_F(ManagerTest, TestDBusMethods)
390 {
391     std::unique_ptr<DataInterfaceBase> dataIface =
392         std::make_unique<MockDataInterface>();
393 
394     std::unique_ptr<JournalBase> journal = std::make_unique<MockJournal>();
395 
396     Manager manager{
397         logManager, std::move(dataIface),
398         std::bind(std::mem_fn(&TestLogger::log), &logger, std::placeholders::_1,
399                   std::placeholders::_2, std::placeholders::_3),
400         std::move(journal)};
401 
402     // Create a PEL, write it to a file, and pass that filename into
403     // the create function so there's one in the repo.
404     auto data = pelDataFactory(TestPELType::pelSimple);
405 
406     fs::path pelFilename = makeTempDir() / "rawpel";
407     std::ofstream pelFile{pelFilename};
408     pelFile.write(reinterpret_cast<const char*>(data.data()), data.size());
409     pelFile.close();
410 
411     std::string adItem = "RAWPEL=" + pelFilename.string();
412     std::vector<std::string> additionalData{adItem};
413     std::vector<std::string> associations;
414 
415     manager.create("error message", 42, 0,
416                    phosphor::logging::Entry::Level::Error, additionalData,
417                    associations);
418 
419     // getPELFromOBMCID
420     auto newData = manager.getPELFromOBMCID(42);
421     EXPECT_EQ(newData.size(), data.size());
422 
423     // Read the PEL to get the ID for later
424     PEL pel{newData};
425     auto id = pel.id();
426 
427     EXPECT_THROW(
428         manager.getPELFromOBMCID(id + 1),
429         sdbusplus::xyz::openbmc_project::Common::Error::InvalidArgument);
430 
431     // getPEL
432     auto unixfd = manager.getPEL(id);
433 
434     // Get the size
435     struct stat s;
436     int r = fstat(unixfd, &s);
437     ASSERT_EQ(r, 0);
438     auto size = s.st_size;
439 
440     // Open the FD and check the contents
441     FILE* fp = fdopen(unixfd, "r");
442     ASSERT_NE(fp, nullptr);
443 
444     std::vector<uint8_t> fdData;
445     fdData.resize(size);
446     r = fread(fdData.data(), 1, size, fp);
447     EXPECT_EQ(r, size);
448 
449     EXPECT_EQ(newData, fdData);
450 
451     fclose(fp);
452 
453     // Run the event loop to close the FD
454     sdeventplus::Event e{sdEvent};
455     e.run(std::chrono::milliseconds(1));
456 
457     EXPECT_THROW(
458         manager.getPEL(id + 1),
459         sdbusplus::xyz::openbmc_project::Common::Error::InvalidArgument);
460 
461     // hostAck
462     manager.hostAck(id);
463 
464     EXPECT_THROW(
465         manager.hostAck(id + 1),
466         sdbusplus::xyz::openbmc_project::Common::Error::InvalidArgument);
467 
468     // hostReject
469     manager.hostReject(id, Manager::RejectionReason::BadPEL);
470 
471     // Run the event loop to log the bad PEL event
472     e.run(std::chrono::milliseconds(1));
473 
474     EXPECT_EQ(logger.errName, "org.open_power.Logging.Error.SentBadPELToHost");
475     EXPECT_EQ(id, std::stoi(logger.ad["BAD_ID"], nullptr, 16));
476 
477     manager.hostReject(id, Manager::RejectionReason::HostFull);
478 
479     EXPECT_THROW(
480         manager.hostReject(id + 1, Manager::RejectionReason::BadPEL),
481         sdbusplus::xyz::openbmc_project::Common::Error::InvalidArgument);
482 
483     // GetPELIdFromBMCLogId
484     EXPECT_EQ(pel.id(), manager.getPELIdFromBMCLogId(pel.obmcLogID()));
485     EXPECT_THROW(
486         manager.getPELIdFromBMCLogId(pel.obmcLogID() + 1),
487         sdbusplus::xyz::openbmc_project::Common::Error::InvalidArgument);
488 
489     // GetBMCLogIdFromPELId
490     EXPECT_EQ(pel.obmcLogID(), manager.getBMCLogIdFromPELId(pel.id()));
491     EXPECT_THROW(
492         manager.getBMCLogIdFromPELId(pel.id() + 1),
493         sdbusplus::xyz::openbmc_project::Common::Error::InvalidArgument);
494 }
495 
496 // An ESEL from the wild
497 const std::string esel{
498     "00 00 df 00 00 00 00 20 00 04 12 01 6f aa 00 00 "
499     "50 48 00 30 01 00 33 00 20 23 05 11 10 20 20 00 00 00 00 07 5c d5 50 db "
500     "42 00 00 10 00 00 00 00 00 00 00 00 00 00 00 00 90 00 00 4e 90 00 00 4e "
501     "55 48 00 18 01 00 09 00 8a 03 40 00 00 00 00 00 ff ff 00 00 00 00 00 00 "
502     "50 53 00 50 01 01 00 00 02 00 00 09 33 2d 00 48 00 00 00 e0 00 00 10 00 "
503     "00 00 00 00 00 20 00 00 00 0c 00 02 00 00 00 fa 00 00 0c e4 00 00 00 12 "
504     "42 43 38 41 33 33 32 44 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 "
505     "20 20 20 20 20 20 20 20 55 44 00 1c 01 06 01 00 02 54 41 4b 00 00 00 06 "
506     "00 00 00 55 00 01 f9 20 00 00 00 00 55 44 00 24 01 06 01 00 01 54 41 4b "
507     "00 00 00 05 00 00 00 00 00 00 00 00 00 00 00 00 23 01 00 02 00 05 00 00 "
508     "55 44 00 0c 01 0b 01 00 0f 01 00 00 55 44 00 10 01 04 01 00 0f 9f de 6a "
509     "00 01 00 00 55 44 00 7c 00 0c 01 00 00 13 0c 02 00 fa 0c e4 16 00 01 2c "
510     "0c 1c 16 00 00 fa 0a f0 14 00 00 fa 0b b8 14 00 00 be 09 60 12 00 01 2c "
511     "0d 7a 12 00 00 fa 0c 4e 10 00 00 fa 0c e4 10 00 00 be 0a 8c 16 00 01 2c "
512     "0c 1c 16 00 01 09 09 f6 16 00 00 fa 09 f6 14 00 00 fa 0b b8 14 00 00 fa "
513     "0a f0 14 00 00 be 08 ca 12 00 01 2c 0c e4 12 00 00 fa 0b 54 10 00 00 fa "
514     "0c 2d 10 00 00 be 08 ca 55 44 00 58 01 03 01 00 00 00 00 00 00 05 31 64 "
515     "00 00 00 00 00 05 0d d4 00 00 00 00 40 5f 06 e0 00 00 00 00 40 5d d2 00 "
516     "00 00 00 00 40 57 d3 d0 00 00 00 00 40 58 f6 a0 00 00 00 00 40 54 c9 34 "
517     "00 00 00 00 40 55 9a 10 00 00 00 00 40 4c 0a 80 00 00 00 00 00 00 27 14 "
518     "55 44 01 84 01 01 01 00 48 6f 73 74 62 6f 6f 74 20 42 75 69 6c 64 20 49 "
519     "44 3a 20 68 6f 73 74 62 6f 6f 74 2d 66 65 63 37 34 64 66 2d 70 30 61 38 "
520     "37 64 63 34 2f 68 62 69 63 6f 72 65 2e 62 69 6e 00 49 42 4d 2d 77 69 74 "
521     "68 65 72 73 70 6f 6f 6e 2d 4f 50 39 2d 76 32 2e 34 2d 39 2e 32 33 34 0a "
522     "09 6f 70 2d 62 75 69 6c 64 2d 38 32 66 34 63 66 30 0a 09 62 75 69 6c 64 "
523     "72 6f 6f 74 2d 32 30 31 39 2e 30 35 2e 32 2d 31 30 2d 67 38 39 35 39 31 "
524     "31 34 0a 09 73 6b 69 62 6f 6f 74 2d 76 36 2e 35 2d 31 38 2d 67 34 37 30 "
525     "66 66 62 35 66 32 39 64 37 0a 09 68 6f 73 74 62 6f 6f 74 2d 66 65 63 37 "
526     "34 64 66 2d 70 30 61 38 37 64 63 34 0a 09 6f 63 63 2d 65 34 35 39 37 61 "
527     "62 0a 09 6c 69 6e 75 78 2d 35 2e 32 2e 31 37 2d 6f 70 65 6e 70 6f 77 65 "
528     "72 31 2d 70 64 64 63 63 30 33 33 0a 09 70 65 74 69 74 62 6f 6f 74 2d 76 "
529     "31 2e 31 30 2e 34 0a 09 6d 61 63 68 69 6e 65 2d 78 6d 6c 2d 63 36 32 32 "
530     "63 62 35 2d 70 37 65 63 61 62 33 64 0a 09 68 6f 73 74 62 6f 6f 74 2d 62 "
531     "69 6e 61 72 69 65 73 2d 36 36 65 39 61 36 30 0a 09 63 61 70 70 2d 75 63 "
532     "6f 64 65 2d 70 39 2d 64 64 32 2d 76 34 0a 09 73 62 65 2d 36 30 33 33 30 "
533     "65 30 0a 09 68 63 6f 64 65 2d 68 77 30 39 32 31 31 39 61 2e 6f 70 6d 73 "
534     "74 0a 00 00 55 44 00 70 01 04 01 00 0f 9f de 6a 00 05 00 00 07 5f 1d f4 "
535     "30 32 43 59 34 37 30 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 "
536     "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 "
537     "0b ac 54 02 59 41 31 39 33 34 36 39 37 30 35 38 00 00 00 00 00 00 05 22 "
538     "a1 58 01 8a 00 58 40 20 17 18 4d 2c 00 00 00 fc 01 a1 00 00 55 44 00 14 "
539     "01 08 01 00 00 00 00 01 00 00 00 5a 00 00 00 05 55 44 03 fc 01 15 31 00 "
540     "01 28 00 42 46 41 50 49 00 00 00 00 00 00 00 00 00 00 00 00 00 00 03 f4 "
541     "00 00 00 00 00 00 03 f4 00 00 00 0b 00 00 00 00 00 00 00 3d 2c 9b c2 84 "
542     "00 00 01 e4 00 48 43 4f fb ed 70 b1 00 00 02 01 00 00 00 00 00 00 00 09 "
543     "00 00 00 00 00 11 bd 20 00 00 00 00 00 01 f8 80 00 00 00 00 00 00 00 01 "
544     "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 16 00 00 00 00 00 00 01 2c "
545     "00 00 00 00 00 00 07 d0 00 00 00 00 00 00 0c 1c 00 00 00 64 00 00 00 3d "
546     "2c 9b d1 11 00 00 01 e4 00 48 43 4f fb ed 70 b1 00 00 02 01 00 00 00 00 "
547     "00 00 00 0a 00 00 00 00 00 13 b5 a0 00 00 00 00 00 01 f8 80 00 00 00 00 "
548     "00 00 00 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 10 00 00 00 00 "
549     "00 00 00 be 00 00 00 00 00 00 07 d0 00 00 00 00 00 00 0a 8c 00 00 00 64 "
550     "00 00 00 3d 2c 9b df 98 00 00 01 e4 00 48 43 4f fb ed 70 b1 00 00 02 01 "
551     "00 00 00 00 00 00 00 0b 00 00 00 00 00 15 ae 20 00 00 00 00 00 01 f8 80 "
552     "00 00 00 00 00 00 00 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 10 "
553     "00 00 00 00 00 00 00 fa 00 00 00 00 00 00 07 d0 00 00 00 00 00 00 0c e4 "
554     "00 00 00 64 00 00 00 3d 2c 9b ea b7 00 00 01 e4 00 48 43 4f fb ed 70 b1 "
555     "00 00 02 01 00 00 00 00 00 00 00 0c 00 00 00 00 00 17 a6 a0 00 00 00 00 "
556     "00 01 f8 80 00 00 00 00 00 00 00 01 00 00 00 00 00 00 00 00 00 00 00 00 "
557     "00 00 00 12 00 00 00 00 00 00 00 fa 00 00 00 00 00 00 07 d0 00 00 00 00 "
558     "00 00 0c 4e 00 00 00 64 00 00 00 3d 2c 9b f6 27 00 00 01 e4 00 48 43 4f "
559     "fb ed 70 b1 00 00 02 01 00 00 00 00 00 00 00 0d 00 00 00 00 00 19 9f 20 "
560     "00 00 00 00 00 01 f8 80 00 00 00 00 00 00 00 01 00 00 00 00 00 00 00 00 "
561     "00 00 00 00 00 00 00 12 00 00 00 00 00 00 01 2c 00 00 00 00 00 00 07 d0 "
562     "00 00 00 00 00 00 0d 7a 00 00 00 64 00 00 00 3d 2c 9c 05 75 00 00 01 e4 "
563     "00 48 43 4f fb ed 70 b1 00 00 02 01 00 00 00 00 00 00 00 0e 00 00 00 00 "
564     "00 1b 97 a0 00 00 00 00 00 01 f8 80 00 00 00 00 00 00 00 01 00 00 00 00 "
565     "00 00 00 00 00 00 00 00 00 00 00 14 00 00 00 00 00 00 00 be 00 00 00 00 "
566     "00 00 07 d0 00 00 00 00 00 00 09 60 00 00 00 64 00 00 00 3d 2c 9c 11 29 "
567     "00 00 01 e4 00 48 43 4f fb ed 70 b1 00 00 02 01 00 00 00 00 00 00 00 0f "
568     "00 00 00 00 00 1d 90 20 00 00 00 00 00 01 f8 80 00 00 00 00 00 00 00 01 "
569     "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 14 00 00 00 00 00 00 00 fa "
570     "00 00 00 00 00 00 07 d0 00 00 00 00 00 00 0b b8 00 00 00 64 00 00 00 3d "
571     "2c 9c 1c 45 00 00 01 e4 00 48 43 4f fb ed 70 b1 00 00 02 01 00 00 00 00 "
572     "00 00 00 10 00 00 00 00 00 1f 88 a0 00 00 00 00 00 01 f8 80 00 00 00 00 "
573     "00 00 00 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 16 00 00 00 00 "
574     "00 00 00 fa 00 00 00 00 00 00 07 d0 00 00 00 00 00 00 0a f0 00 00 00 64 "
575     "00 00 00 3d 2c 9c 2b 14 00 00 01 e4 00 48 43 4f fb ed 70 b1 00 00 02 01 "
576     "00 00 00 00 00 00 00 11 00 00 00 00 00 21 81 20 00 00 00 00 00 01 f8 80 "
577     "00 00 00 00 00 00 00 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 16 "
578     "00 00 00 00 00 00 01 2c 00 00 00 00 00 00 07 d0 00 00 00 00 00 00 0c 1c "
579     "00 00 00 64 00 00 00 3d 2d 6d 8f 9e 00 00 01 e4 00 00 43 4f 52 d7 9c 36 "
580     "00 00 04 73 00 00 00 1c 00 00 00 3d 2d 6d 99 ac 00 00 01 e4 00 10 43 4f "
581     "3f f2 02 3d 00 00 05 58 00 00 00 00 02 00 00 01 00 00 00 00 00 00 00 40 "
582     "00 00 00 2c 55 44 00 30 01 15 31 00 01 28 00 42 46 41 50 49 5f 44 42 47 "
583     "00 00 00 00 00 00 00 00 00 00 00 28 00 00 00 00 00 00 00 28 00 00 00 00 "
584     "00 00 00 00 55 44 01 74 01 15 31 00 01 28 00 42 46 41 50 49 5f 49 00 00 "
585     "00 00 00 00 00 00 00 00 00 00 01 6c 00 00 00 00 00 00 01 6c 00 00 00 0b "
586     "00 00 00 00 00 00 00 3c 0d 52 18 5e 00 00 01 e4 00 08 43 4f 46 79 94 13 "
587     "00 00 0a 5b 00 00 00 00 00 00 2c 00 00 00 00 24 00 00 00 3c 0d 6b 26 6c "
588     "00 00 01 e4 00 00 43 4f 4e 9b 18 74 00 00 01 03 00 00 00 1c 00 00 00 3c "
589     "12 b9 2d 13 00 00 01 e4 00 00 43 4f ea 31 ed d4 00 00 05 c4 00 00 00 1c "
590     "00 00 00 3c 13 02 73 53 00 00 01 e4 00 00 43 4f ea 31 ed d4 00 00 05 c4 "
591     "00 00 00 1c 00 00 00 3c 13 04 7c 94 00 00 01 e4 00 00 43 4f ea 31 ed d4 "
592     "00 00 05 c4 00 00 00 1c 00 00 00 3c 13 06 ad e1 00 00 01 e4 00 00 43 4f "
593     "ea 31 ed d4 00 00 05 c4 00 00 00 1c 00 00 00 3c 13 07 3f 77 00 00 01 e4 "
594     "00 00 43 4f 5e 4a 55 32 00 00 10 f2 00 00 00 1c 00 00 00 3c 13 07 4e e4 "
595     "00 00 01 e4 00 00 43 4f 5e 4a 55 32 00 00 0d 68 00 00 00 1c 00 00 00 3c "
596     "13 36 79 18 00 00 01 e4 00 00 43 4f ea 31 ed d4 00 00 05 c4 00 00 00 1c "
597     "00 00 00 3d 2c 9c 36 70 00 00 01 e4 00 00 43 4f 23 45 90 97 00 00 02 47 "
598     "00 00 00 1c 00 00 00 3d 2d 6d a3 ed 00 00 01 e4 00 08 43 4f 74 3a 5b 1a "
599     "00 00 04 cc 00 00 00 00 02 00 00 01 00 00 00 24 55 44 00 30 01 15 31 00 "
600     "01 28 00 42 53 43 41 4e 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 28 "
601     "00 00 00 00 00 00 00 28 00 00 00 00 00 00 00 00"};
602 
603 TEST_F(ManagerTest, TestESELToRawData)
604 {
605     auto data = Manager::eselToRawData(esel);
606 
607     EXPECT_EQ(data.size(), 2464);
608 
609     PEL pel{data};
610     EXPECT_TRUE(pel.valid());
611 }
612 
613 TEST_F(ManagerTest, TestCreateWithESEL)
614 {
615     std::unique_ptr<DataInterfaceBase> dataIface =
616         std::make_unique<MockDataInterface>();
617 
618     std::unique_ptr<JournalBase> journal = std::make_unique<MockJournal>();
619 
620     openpower::pels::Manager manager{
621         logManager, std::move(dataIface),
622         std::bind(std::mem_fn(&TestLogger::log), &logger, std::placeholders::_1,
623                   std::placeholders::_2, std::placeholders::_3),
624         std::move(journal)};
625 
626     {
627         std::string adItem = "ESEL=" + esel;
628         std::vector<std::string> additionalData{adItem};
629         std::vector<std::string> associations;
630 
631         manager.create("error message", 37, 0,
632                        phosphor::logging::Entry::Level::Error, additionalData,
633                        associations);
634 
635         auto data = manager.getPELFromOBMCID(37);
636         PEL pel{data};
637         EXPECT_TRUE(pel.valid());
638     }
639 
640     // Now an invalid one
641     {
642         std::string adItem = "ESEL=" + esel;
643 
644         // Crop it
645         adItem.resize(adItem.size() - 300);
646 
647         std::vector<std::string> additionalData{adItem};
648         std::vector<std::string> associations;
649 
650         manager.create("error message", 38, 0,
651                        phosphor::logging::Entry::Level::Error, additionalData,
652                        associations);
653 
654         EXPECT_THROW(
655             manager.getPELFromOBMCID(38),
656             sdbusplus::xyz::openbmc_project::Common::Error::InvalidArgument);
657 
658         // Run the event loop to log the bad PEL event
659         sdeventplus::Event e{sdEvent};
660         e.run(std::chrono::milliseconds(1));
661 
662         EXPECT_EQ(logger.errName, "org.open_power.Logging.Error.BadHostPEL");
663         EXPECT_EQ(logger.errLevel, phosphor::logging::Entry::Level::Error);
664     }
665 }
666 
667 // Test that PELs will be pruned when necessary
668 TEST_F(ManagerTest, TestPruning)
669 {
670     sdeventplus::Event e{sdEvent};
671 
672     std::unique_ptr<DataInterfaceBase> dataIface =
673         std::make_unique<MockDataInterface>();
674 
675     std::unique_ptr<JournalBase> journal = std::make_unique<MockJournal>();
676 
677     openpower::pels::Manager manager{
678         logManager, std::move(dataIface),
679         std::bind(std::mem_fn(&TestLogger::log), &logger, std::placeholders::_1,
680                   std::placeholders::_2, std::placeholders::_3),
681         std::move(journal)};
682 
683     // Create 25 1000B (4096B on disk each, which is what is used for pruning)
684     // BMC non-informational PELs in the 100KB repository.  After the 24th one,
685     // the repo will be 96% full and a prune should be triggered to remove all
686     // but 7 to get under 30% full.  Then when the 25th is added there will be
687     // 8 left.
688 
689     auto dir = makeTempDir();
690     for (int i = 1; i <= 25; i++)
691     {
692         auto data = pelFactory(42, 'O', 0x40, 0x8800, 1000);
693 
694         fs::path pelFilename = dir / "rawpel";
695         std::ofstream pelFile{pelFilename};
696         pelFile.write(reinterpret_cast<const char*>(data.data()), data.size());
697         pelFile.close();
698 
699         std::string adItem = "RAWPEL=" + pelFilename.string();
700         std::vector<std::string> additionalData{adItem};
701         std::vector<std::string> associations;
702 
703         manager.create("error message", 42, 0,
704                        phosphor::logging::Entry::Level::Error, additionalData,
705                        associations);
706 
707         // Simulate the code getting back to the event loop
708         // after each create.
709         e.run(std::chrono::milliseconds(1));
710 
711         if (i < 24)
712         {
713             EXPECT_EQ(countPELsInRepo(), i);
714         }
715         else if (i == 24)
716         {
717             // Prune occured
718             EXPECT_EQ(countPELsInRepo(), 7);
719         }
720         else // i == 25
721         {
722             EXPECT_EQ(countPELsInRepo(), 8);
723         }
724     }
725 
726     try
727     {
728         // Make sure the 8 newest ones are still found.
729         for (uint32_t i = 0; i < 8; i++)
730         {
731             manager.getPEL(0x50000012 + i);
732         }
733     }
734     catch (
735         const sdbusplus::xyz::openbmc_project::Common::Error::InvalidArgument&
736             ex)
737     {
738         ADD_FAILURE() << "PELs should have all been found";
739     }
740 }
741 
742 // Test that manually deleting a PEL file will be recognized by the code.
743 TEST_F(ManagerTest, TestPELManualDelete)
744 {
745     sdeventplus::Event e{sdEvent};
746 
747     std::unique_ptr<DataInterfaceBase> dataIface =
748         std::make_unique<MockDataInterface>();
749 
750     std::unique_ptr<JournalBase> journal = std::make_unique<MockJournal>();
751 
752     openpower::pels::Manager manager{
753         logManager, std::move(dataIface),
754         std::bind(std::mem_fn(&TestLogger::log), &logger, std::placeholders::_1,
755                   std::placeholders::_2, std::placeholders::_3),
756         std::move(journal)};
757 
758     auto data = pelDataFactory(TestPELType::pelSimple);
759     auto dir = makeTempDir();
760     fs::path pelFilename = dir / "rawpel";
761 
762     std::string adItem = "RAWPEL=" + pelFilename.string();
763     std::vector<std::string> additionalData{adItem};
764     std::vector<std::string> associations;
765 
766     // Add 20 PELs, they will get incrementing IDs like
767     // 0x50000001, 0x50000002, etc.
768     for (int i = 1; i <= 20; i++)
769     {
770         std::ofstream pelFile{pelFilename};
771         pelFile.write(reinterpret_cast<const char*>(data.data()), data.size());
772         pelFile.close();
773 
774         manager.create("error message", 42, 0,
775                        phosphor::logging::Entry::Level::Error, additionalData,
776                        associations);
777 
778         // Sanity check this ID is really there so we can test
779         // it was deleted later.  This will throw an exception if
780         // not present.
781         manager.getPEL(0x50000000 + i);
782 
783         // Run an event loop pass where the internal FD is deleted
784         // after the getPEL function call.
785         e.run(std::chrono::milliseconds(1));
786     }
787 
788     EXPECT_EQ(countPELsInRepo(), 20);
789 
790     deletePELFile(0x50000001);
791 
792     // Run a single event loop pass so the inotify event can run
793     e.run(std::chrono::milliseconds(1));
794 
795     EXPECT_EQ(countPELsInRepo(), 19);
796 
797     EXPECT_THROW(
798         manager.getPEL(0x50000001),
799         sdbusplus::xyz::openbmc_project::Common::Error::InvalidArgument);
800 
801     // Delete a few more, they should all get handled in the same
802     // event loop pass
803     std::vector<uint32_t> toDelete{0x50000002, 0x50000003, 0x50000004,
804                                    0x50000005, 0x50000006};
805     std::for_each(toDelete.begin(), toDelete.end(),
806                   [](auto i) { deletePELFile(i); });
807 
808     e.run(std::chrono::milliseconds(1));
809 
810     EXPECT_EQ(countPELsInRepo(), 14);
811 
812     std::for_each(toDelete.begin(), toDelete.end(), [&manager](const auto i) {
813         EXPECT_THROW(
814             manager.getPEL(i),
815             sdbusplus::xyz::openbmc_project::Common::Error::InvalidArgument);
816     });
817 }
818 
819 // Test that deleting all PELs at once is handled OK.
820 TEST_F(ManagerTest, TestPELManualDeleteAll)
821 {
822     sdeventplus::Event e{sdEvent};
823 
824     std::unique_ptr<DataInterfaceBase> dataIface =
825         std::make_unique<MockDataInterface>();
826 
827     std::unique_ptr<JournalBase> journal = std::make_unique<MockJournal>();
828 
829     openpower::pels::Manager manager{
830         logManager, std::move(dataIface),
831         std::bind(std::mem_fn(&TestLogger::log), &logger, std::placeholders::_1,
832                   std::placeholders::_2, std::placeholders::_3),
833         std::move(journal)};
834 
835     auto data = pelDataFactory(TestPELType::pelSimple);
836     auto dir = makeTempDir();
837     fs::path pelFilename = dir / "rawpel";
838 
839     std::string adItem = "RAWPEL=" + pelFilename.string();
840     std::vector<std::string> additionalData{adItem};
841     std::vector<std::string> associations;
842 
843     // Add 200 PELs, they will get incrementing IDs like
844     // 0x50000001, 0x50000002, etc.
845     for (int i = 1; i <= 200; i++)
846     {
847         std::ofstream pelFile{pelFilename};
848         pelFile.write(reinterpret_cast<const char*>(data.data()), data.size());
849         pelFile.close();
850 
851         manager.create("error message", 42, 0,
852                        phosphor::logging::Entry::Level::Error, additionalData,
853                        associations);
854 
855         // Sanity check this ID is really there so we can test
856         // it was deleted later.  This will throw an exception if
857         // not present.
858         manager.getPEL(0x50000000 + i);
859 
860         // Run an event loop pass where the internal FD is deleted
861         // after the getPEL function call.
862         e.run(std::chrono::milliseconds(1));
863     }
864 
865     // Delete them all at once
866     auto logPath = getPELRepoPath() / "logs";
867     std::string cmd = "rm " + logPath.string() + "/*_*";
868 
869     {
870         auto rc = system(cmd.c_str());
871         EXPECT_EQ(rc, 0);
872     }
873 
874     EXPECT_EQ(countPELsInRepo(), 0);
875 
876     // It will take 5 event loop passes to process them all
877     for (int i = 0; i < 5; i++)
878     {
879         e.run(std::chrono::milliseconds(1));
880     }
881 
882     for (int i = 1; i <= 200; i++)
883     {
884         EXPECT_THROW(
885             manager.getPEL(0x50000000 + i),
886             sdbusplus::xyz::openbmc_project::Common::Error::InvalidArgument);
887     }
888 }
889 
890 // Test that fault LEDs are turned on when PELs are created
891 TEST_F(ManagerTest, TestServiceIndicators)
892 {
893     std::unique_ptr<DataInterfaceBase> dataIface =
894         std::make_unique<MockDataInterface>();
895 
896     MockDataInterface* mockIface =
897         reinterpret_cast<MockDataInterface*>(dataIface.get());
898 
899     std::unique_ptr<JournalBase> journal = std::make_unique<MockJournal>();
900 
901     openpower::pels::Manager manager{
902         logManager, std::move(dataIface),
903         std::bind(std::mem_fn(&TestLogger::log), &logger, std::placeholders::_1,
904                   std::placeholders::_2, std::placeholders::_3),
905         std::move(journal)};
906 
907     // Add a PEL with a callout as if hostboot added it
908     {
909         EXPECT_CALL(*mockIface, getInventoryFromLocCode("U42", 0, true))
910             .WillOnce(
911                 Return(std::vector<std::string>{"/system/chassis/processor"}));
912 
913         EXPECT_CALL(*mockIface,
914                     setFunctional("/system/chassis/processor", false))
915             .Times(1);
916 
917         // This hostboot PEL has a single hardware callout in it.
918         auto data = pelFactory(1, 'B', 0x20, 0xA400, 500);
919 
920         fs::path pelFilename = makeTempDir() / "rawpel";
921         std::ofstream pelFile{pelFilename};
922         pelFile.write(reinterpret_cast<const char*>(data.data()), data.size());
923         pelFile.close();
924 
925         std::string adItem = "RAWPEL=" + pelFilename.string();
926         std::vector<std::string> additionalData{adItem};
927         std::vector<std::string> associations;
928 
929         manager.create("error message", 42, 0,
930                        phosphor::logging::Entry::Level::Error, additionalData,
931                        associations);
932     }
933 
934     // Add a BMC PEL with a callout that uses the message registry
935     {
936         std::vector<std::string> names{"systemA"};
937         EXPECT_CALL(*mockIface, getSystemNames)
938             .Times(1)
939             .WillOnce(Return(names));
940 
941         EXPECT_CALL(*mockIface, expandLocationCode("P42-C23", 0))
942             .WillOnce(Return("U42-P42-C23"));
943 
944         // First call to this is when building the Callout section
945         EXPECT_CALL(*mockIface, getInventoryFromLocCode("P42-C23", 0, false))
946             .WillOnce(
947                 Return(std::vector<std::string>{"/system/chassis/processor"}));
948 
949         // Second call to this is finding the associated LED group
950         EXPECT_CALL(*mockIface, getInventoryFromLocCode("U42-P42-C23", 0, true))
951             .WillOnce(
952                 Return(std::vector<std::string>{"/system/chassis/processor"}));
953 
954         EXPECT_CALL(*mockIface,
955                     setFunctional("/system/chassis/processor", false))
956             .Times(1);
957 
958         const auto registry = R"(
959         {
960             "PELs":
961             [
962                 {
963                     "Name": "xyz.openbmc_project.Error.Test",
964                     "Subsystem": "power_supply",
965                     "ActionFlags": ["service_action", "report"],
966                     "SRC":
967                     {
968                         "ReasonCode": "0x2030"
969                     },
970                     "Callouts": [
971                         {
972                             "CalloutList": [
973                                 {"Priority": "high", "LocCode": "P42-C23"}
974                             ]
975                         }
976                     ],
977                     "Documentation":
978                     {
979                         "Description": "Test Error",
980                         "Message": "Test Error"
981                     }
982                 }
983             ]
984         })";
985 
986         auto path = getPELReadOnlyDataPath();
987         fs::create_directories(path);
988         path /= "message_registry.json";
989 
990         std::ofstream registryFile{path};
991         registryFile << registry;
992         registryFile.close();
993 
994         std::vector<std::string> additionalData;
995         std::vector<std::string> associations;
996 
997         manager.create("xyz.openbmc_project.Error.Test", 42, 0,
998                        phosphor::logging::Entry::Level::Error, additionalData,
999                        associations);
1000     }
1001 }
1002 
1003 // Test for duplicate PELs moved to archive folder
1004 TEST_F(ManagerTest, TestDuplicatePEL)
1005 {
1006     sdeventplus::Event e{sdEvent};
1007     size_t count = 0;
1008 
1009     std::unique_ptr<DataInterfaceBase> dataIface =
1010         std::make_unique<MockDataInterface>();
1011 
1012     std::unique_ptr<JournalBase> journal = std::make_unique<MockJournal>();
1013 
1014     openpower::pels::Manager manager{
1015         logManager, std::move(dataIface),
1016         std::bind(std::mem_fn(&TestLogger::log), &logger, std::placeholders::_1,
1017                   std::placeholders::_2, std::placeholders::_3),
1018         std::move(journal)};
1019 
1020     for (int i = 0; i < 2; i++)
1021     {
1022         // This hostboot PEL has a single hardware callout in it.
1023         auto data = pelFactory(1, 'B', 0x20, 0xA400, 500);
1024 
1025         fs::path pelFilename = makeTempDir() / "rawpel";
1026         std::ofstream pelFile{pelFilename};
1027         pelFile.write(reinterpret_cast<const char*>(data.data()), data.size());
1028         pelFile.close();
1029 
1030         std::string adItem = "RAWPEL=" + pelFilename.string();
1031         std::vector<std::string> additionalData{adItem};
1032         std::vector<std::string> associations;
1033 
1034         manager.create("error message", 42, 0,
1035                        phosphor::logging::Entry::Level::Error, additionalData,
1036                        associations);
1037 
1038         e.run(std::chrono::milliseconds(1));
1039     }
1040 
1041     for (auto& f :
1042          fs::directory_iterator(getPELRepoPath() / "logs" / "archive"))
1043     {
1044         if (fs::is_regular_file(f.path()))
1045         {
1046             count++;
1047         }
1048     }
1049 
1050     // Get count of PELs in the repository & in archive directtory
1051     EXPECT_EQ(countPELsInRepo(), 1);
1052     EXPECT_EQ(count, 1);
1053 }
1054 
1055 // Test termination bit set for pel with critical system termination severity
1056 TEST_F(ManagerTest, TestTerminateBitWithPELSevCriticalSysTerminate)
1057 {
1058     const auto registry = R"(
1059 {
1060     "PELs":
1061     [
1062         {
1063             "Name": "xyz.openbmc_project.Error.Test",
1064             "Subsystem": "power_supply",
1065             "Severity": "critical_system_term",
1066             "ActionFlags": ["service_action", "report"],
1067             "SRC":
1068             {
1069                 "ReasonCode": "0x2030"
1070             },
1071             "Documentation":
1072             {
1073                 "Description": "A PGOOD Fault",
1074                 "Message": "PS had a PGOOD Fault"
1075             }
1076         }
1077     ]
1078 }
1079 )";
1080 
1081     auto path = getPELReadOnlyDataPath();
1082     fs::create_directories(path);
1083     path /= "message_registry.json";
1084 
1085     std::ofstream registryFile{path};
1086     registryFile << registry;
1087     registryFile.close();
1088 
1089     std::unique_ptr<DataInterfaceBase> dataIface =
1090         std::make_unique<MockDataInterface>();
1091 
1092     std::unique_ptr<JournalBase> journal = std::make_unique<MockJournal>();
1093 
1094     openpower::pels::Manager manager{
1095         logManager, std::move(dataIface),
1096         std::bind(std::mem_fn(&TestLogger::log), &logger, std::placeholders::_1,
1097                   std::placeholders::_2, std::placeholders::_3),
1098         std::move(journal)};
1099 
1100     std::vector<std::string> additionalData{"FOO=BAR"};
1101     std::vector<std::string> associations;
1102 
1103     // Create the event log to create the PEL from.
1104     manager.create("xyz.openbmc_project.Error.Test", 33, 0,
1105                    phosphor::logging::Entry::Level::Error, additionalData,
1106                    associations);
1107 
1108     // Ensure a PEL was created in the repository
1109     auto pelData = findAnyPELInRepo();
1110     ASSERT_TRUE(pelData);
1111 
1112     auto getPELData = readPELFile(*pelData);
1113     PEL pel(*getPELData);
1114 
1115     // Spot check it.  Other testcases cover the details.
1116     EXPECT_TRUE(pel.valid());
1117 
1118     // Check for terminate bit set
1119     auto& hexwords = pel.primarySRC().value()->hexwordData();
1120     EXPECT_EQ(hexwords[3] & 0x20000000, 0x20000000);
1121 }
1122 
1123 TEST_F(ManagerTest, TestSanitizeFieldforDBus)
1124 {
1125     std::string base{"(test0!}\n\t ~"};
1126     auto string = base;
1127     string += char{' ' - 1};
1128     string += char{'~' + 1};
1129     string += char{0};
1130     string += char{static_cast<char>(0xFF)};
1131 
1132     // convert the last four chars to spaces
1133     EXPECT_EQ(Manager::sanitizeFieldForDBus(string), base + "    ");
1134 }
1135 
1136 TEST_F(ManagerTest, TestFruPlug)
1137 {
1138     const auto registry = R"(
1139 {
1140     "PELs":
1141     [{
1142         "Name": "xyz.openbmc_project.Fan.Error.Fault",
1143         "Subsystem": "power_fans",
1144         "ComponentID": "0x2800",
1145         "SRC":
1146         {
1147             "Type": "11",
1148             "ReasonCode": "0x76F0",
1149             "Words6To9": {},
1150             "DeconfigFlag": true
1151         },
1152         "Callouts": [{
1153                 "CalloutList": [
1154                     {"Priority": "low", "LocCode": "P0"},
1155                     {"Priority": "high", "LocCode": "A3"}
1156                 ]
1157             }],
1158         "Documentation": {
1159             "Description": "A Fan Fault",
1160             "Message": "Fan had a Fault"
1161         }
1162      }]
1163 }
1164 )";
1165 
1166     auto path = getPELReadOnlyDataPath();
1167     fs::create_directories(path);
1168     path /= "message_registry.json";
1169 
1170     std::ofstream registryFile{path};
1171     registryFile << registry;
1172     registryFile.close();
1173 
1174     std::unique_ptr<DataInterfaceBase> dataIface =
1175         std::make_unique<MockDataInterface>();
1176 
1177     MockDataInterface* mockIface =
1178         reinterpret_cast<MockDataInterface*>(dataIface.get());
1179 
1180     // Set up the mock calls used when building callouts
1181     EXPECT_CALL(*mockIface, getInventoryFromLocCode("P0", 0, false))
1182         .WillRepeatedly(Return(std::vector<std::string>{"motherboard"}));
1183     EXPECT_CALL(*mockIface, expandLocationCode("P0", 0))
1184         .WillRepeatedly(Return("U1234-P0"));
1185     EXPECT_CALL(*mockIface, getInventoryFromLocCode("U1234-P0", 0, true))
1186         .WillRepeatedly(Return(std::vector<std::string>{"motherboard"}));
1187 
1188     EXPECT_CALL(*mockIface, getInventoryFromLocCode("A3", 0, false))
1189         .WillRepeatedly(Return(std::vector<std::string>{"fan"}));
1190     EXPECT_CALL(*mockIface, expandLocationCode("A3", 0))
1191         .WillRepeatedly(Return("U1234-A3"));
1192     EXPECT_CALL(*mockIface, getInventoryFromLocCode("U1234-A3", 0, true))
1193         .WillRepeatedly(Return(std::vector<std::string>{"fan"}));
1194 
1195     std::unique_ptr<JournalBase> journal = std::make_unique<MockJournal>();
1196 
1197     openpower::pels::Manager manager{
1198         logManager, std::move(dataIface),
1199         std::bind(std::mem_fn(&TestLogger::log), &logger, std::placeholders::_1,
1200                   std::placeholders::_2, std::placeholders::_3),
1201         std::move(journal)};
1202 
1203     std::vector<std::string> additionalData;
1204     std::vector<std::string> associations;
1205 
1206     auto checkDeconfigured = [](bool deconfigured) {
1207         auto pelFile = findAnyPELInRepo();
1208         ASSERT_TRUE(pelFile);
1209 
1210         auto data = readPELFile(*pelFile);
1211         PEL pel(*data);
1212         ASSERT_TRUE(pel.valid());
1213 
1214         EXPECT_EQ(pel.primarySRC().value()->getErrorStatusFlag(
1215                       SRC::ErrorStatusFlags::deconfigured),
1216                   deconfigured);
1217     };
1218 
1219     manager.create("xyz.openbmc_project.Fan.Error.Fault", 42, 0,
1220                    phosphor::logging::Entry::Level::Error, additionalData,
1221                    associations);
1222     checkDeconfigured(true);
1223 
1224     // Replace A3 so PEL deconfigured flag should be set to false
1225     mockIface->fruPresent("U1234-A3");
1226     checkDeconfigured(false);
1227 
1228     manager.erase(42);
1229 
1230     // Create it again and replace a FRU not in the callout list.
1231     // Deconfig flag should stay on.
1232     manager.create("xyz.openbmc_project.Fan.Error.Fault", 43, 0,
1233                    phosphor::logging::Entry::Level::Error, additionalData,
1234                    associations);
1235     checkDeconfigured(true);
1236     mockIface->fruPresent("U1234-A4");
1237     checkDeconfigured(true);
1238 }
1239 
1240 int createHWIsolatedCalloutFile()
1241 {
1242     json jsonCalloutDataList(nlohmann::json::value_t::array);
1243     json jsonDimmCallout;
1244 
1245     jsonDimmCallout["LocationCode"] = "Ufcs-DIMM0";
1246     jsonDimmCallout["EntityPath"] = {35, 1, 0, 2, 0, 3, 0, 0, 0, 0, 0,
1247                                      0,  0, 0, 0, 0, 0, 0, 0, 0, 0};
1248     jsonDimmCallout["GuardType"] = "GARD_Predictive";
1249     jsonDimmCallout["Deconfigured"] = false;
1250     jsonDimmCallout["Guarded"] = true;
1251     jsonDimmCallout["Priority"] = "M";
1252     jsonCalloutDataList.emplace_back(std::move(jsonDimmCallout));
1253 
1254     std::string calloutData(jsonCalloutDataList.dump());
1255     std::string calloutFile("/tmp/phalPELCalloutsJson.XXXXXX");
1256     int fileFD = -1;
1257 
1258     fileFD = mkostemp(calloutFile.data(), O_RDWR);
1259     if (fileFD == -1)
1260     {
1261         perror("Failed to create PELCallouts file");
1262         return -1;
1263     }
1264 
1265     ssize_t rc = write(fileFD, calloutData.c_str(), calloutData.size());
1266     if (rc == -1)
1267     {
1268         perror("Failed to write PELCallouts file");
1269         close(fileFD);
1270         return -1;
1271     }
1272 
1273     // Ensure we seek to the beginning of the file
1274     rc = lseek(fileFD, 0, SEEK_SET);
1275     if (rc == -1)
1276     {
1277         perror("Failed to set SEEK_SET for PELCallouts file");
1278         close(fileFD);
1279         return -1;
1280     }
1281     return fileFD;
1282 }
1283 
1284 void appendFFDCEntry(int fd, uint8_t subTypeJson, uint8_t version,
1285                      phosphor::logging::FFDCEntries& ffdcEntries)
1286 {
1287     phosphor::logging::FFDCEntry ffdcEntry =
1288         std::make_tuple(sdbusplus::xyz::openbmc_project::Logging::server::
1289                             Create::FFDCFormat::JSON,
1290                         subTypeJson, version, fd);
1291     ffdcEntries.push_back(ffdcEntry);
1292 }
1293 
1294 TEST_F(ManagerTest, TestPELDeleteWithoutHWIsolation)
1295 {
1296     const auto registry = R"(
1297     {
1298         "PELs":
1299         [{
1300             "Name": "xyz.openbmc_project.Error.Test",
1301             "SRC":
1302             {
1303                 "ReasonCode": "0x2030"
1304             },
1305             "Documentation": {
1306                 "Description": "Test Error",
1307                 "Message": "Test Error"
1308             }
1309         }]
1310     }
1311     )";
1312 
1313     auto path = getPELReadOnlyDataPath();
1314     fs::create_directories(path);
1315     path /= "message_registry.json";
1316 
1317     std::ofstream registryFile{path};
1318     registryFile << registry;
1319     registryFile.close();
1320 
1321     std::unique_ptr<DataInterfaceBase> dataIface =
1322         std::make_unique<MockDataInterface>();
1323 
1324     MockDataInterface* mockIface =
1325         reinterpret_cast<MockDataInterface*>(dataIface.get());
1326 
1327     EXPECT_CALL(*mockIface, getInventoryFromLocCode("Ufcs-DIMM0", 0, false))
1328         .WillOnce(Return(std::vector<std::string>{
1329             "/xyz/openbmc_project/inventory/system/chassis/motherboard/dimm0"}));
1330 
1331     // Mock the scenario where the hardware isolation guard is flagged
1332     // but is not associated, resulting in an empty list being returned.
1333     EXPECT_CALL(
1334         *mockIface,
1335         getAssociatedPaths(
1336             ::testing::StrEq(
1337                 "/xyz/openbmc_project/logging/entry/42/isolated_hw_entry"),
1338             ::testing::StrEq("/"), 0,
1339             ::testing::ElementsAre(
1340                 "xyz.openbmc_project.HardwareIsolation.Entry")))
1341         .WillRepeatedly(Return(std::vector<std::string>{}));
1342 
1343     std::unique_ptr<JournalBase> journal = std::make_unique<MockJournal>();
1344     openpower::pels::Manager manager{
1345         logManager, std::move(dataIface),
1346         std::bind(std::mem_fn(&TestLogger::log), &logger, std::placeholders::_1,
1347                   std::placeholders::_2, std::placeholders::_3),
1348         std::move(journal)};
1349     std::vector<std::string> additionalData;
1350     std::vector<std::string> associations;
1351 
1352     // Check when there's no PEL with given id.
1353     {
1354         EXPECT_FALSE(manager.isDeleteProhibited(42));
1355     }
1356     // creating without ffdcEntries
1357     manager.create("xyz.openbmc_project.Error.Test", 42, 0,
1358                    phosphor::logging::Entry::Level::Error, additionalData,
1359                    associations);
1360     auto pelFile = findAnyPELInRepo();
1361     auto data = readPELFile(*pelFile);
1362     PEL pel_unguarded(*data);
1363     {
1364         // Verify that the guard flag is false.
1365         EXPECT_FALSE(pel_unguarded.getGuardFlag());
1366         // Check that `isDeleteProhibited` returns false when the guard flag is
1367         // false.
1368         EXPECT_FALSE(manager.isDeleteProhibited(42));
1369     }
1370     manager.erase(42);
1371     EXPECT_FALSE(findAnyPELInRepo());
1372 
1373     int fd = createHWIsolatedCalloutFile();
1374     ASSERT_NE(fd, -1);
1375     uint8_t subTypeJson = 0xCA;
1376     uint8_t version = 0x01;
1377     phosphor::logging::FFDCEntries ffdcEntries;
1378     appendFFDCEntry(fd, subTypeJson, version, ffdcEntries);
1379     manager.create("xyz.openbmc_project.Error.Test", 42, 0,
1380                    phosphor::logging::Entry::Level::Error, additionalData,
1381                    associations, ffdcEntries);
1382     close(fd);
1383 
1384     auto pelPathInRepo = findAnyPELInRepo();
1385     auto unguardedData = readPELFile(*pelPathInRepo);
1386     PEL pel(*unguardedData);
1387     {
1388         // Verify guard flag set to true
1389         EXPECT_TRUE(pel.getGuardFlag());
1390         // Check even if guard flag is true, if dbus call returns empty
1391         // array list then `isDeleteProhibited` returns false
1392         EXPECT_FALSE(manager.isDeleteProhibited(42));
1393     }
1394     manager.erase(42);
1395 }
1396 
1397 TEST_F(ManagerTest, TestPELDeleteWithHWIsolation)
1398 {
1399     const auto registry = R"(
1400     {
1401         "PELs":
1402         [{
1403             "Name": "xyz.openbmc_project.Error.Test",
1404             "Severity": "critical_system_term",
1405             "SRC":
1406             {
1407                 "ReasonCode": "0x2030"
1408             },
1409             "Documentation": {
1410                 "Description": "Test Error",
1411                 "Message": "Test Error"
1412             }
1413         }]
1414     }
1415     )";
1416 
1417     auto path = getPELReadOnlyDataPath();
1418     fs::create_directories(path);
1419     path /= "message_registry.json";
1420 
1421     std::ofstream registryFile{path};
1422     registryFile << registry;
1423     registryFile.close();
1424 
1425     std::unique_ptr<DataInterfaceBase> dataIface =
1426         std::make_unique<MockDataInterface>();
1427 
1428     MockDataInterface* mockIface =
1429         reinterpret_cast<MockDataInterface*>(dataIface.get());
1430 
1431     EXPECT_CALL(*mockIface, getInventoryFromLocCode("Ufcs-DIMM0", 0, false))
1432         .WillOnce(Return(std::vector<std::string>{
1433             "/xyz/openbmc_project/inventory/system/chassis/motherboard/dimm0"}));
1434 
1435     EXPECT_CALL(
1436         *mockIface,
1437         getAssociatedPaths(
1438             ::testing::StrEq(
1439                 "/xyz/openbmc_project/logging/entry/42/isolated_hw_entry"),
1440             ::testing::StrEq("/"), 0,
1441             ::testing::ElementsAre(
1442                 "xyz.openbmc_project.HardwareIsolation.Entry")))
1443         .WillRepeatedly(Return(std::vector<std::string>{
1444             "/xyz/openbmc_project/hardware_isolation/entry/1"}));
1445 
1446     std::unique_ptr<JournalBase> journal = std::make_unique<MockJournal>();
1447     openpower::pels::Manager manager{
1448         logManager, std::move(dataIface),
1449         std::bind(std::mem_fn(&TestLogger::log), &logger, std::placeholders::_1,
1450                   std::placeholders::_2, std::placeholders::_3),
1451         std::move(journal)};
1452     std::vector<std::string> additionalData;
1453     std::vector<std::string> associations;
1454 
1455     int fd = createHWIsolatedCalloutFile();
1456     ASSERT_NE(fd, -1);
1457     uint8_t subTypeJson = 0xCA;
1458     uint8_t version = 0x01;
1459     phosphor::logging::FFDCEntries ffdcEntries;
1460     appendFFDCEntry(fd, subTypeJson, version, ffdcEntries);
1461     manager.create("xyz.openbmc_project.Error.Test", 42, 0,
1462                    phosphor::logging::Entry::Level::Error, additionalData,
1463                    associations, ffdcEntries);
1464     close(fd);
1465 
1466     auto pelFile = findAnyPELInRepo();
1467     EXPECT_TRUE(pelFile);
1468     auto data = readPELFile(*pelFile);
1469     PEL pel(*data);
1470     EXPECT_TRUE(pel.valid());
1471     // Test case where the guard flag is set to true and the hardware isolation
1472     // guard is associated, which should result in `isDeleteProhibited`
1473     // returning true as expected.
1474     EXPECT_TRUE(pel.getGuardFlag());
1475     EXPECT_TRUE(manager.isDeleteProhibited(42));
1476     manager.erase(42);
1477 }
1478