PEL: peltool: Implement SRC regex filtering

Added "-s, --scrub filename" option to ignore PELs matching regular
expression passed through the specified file.

Scrubbing by SRC applies to the -l, -a and -n options of peltool.

\# ./peltool -l -h
{
	"0x50000C00": {
		"SRC": "BD8D1001",
		"Message": "An operation timed out",
		"PLID": "0x50000C00",
		"CreatorID": "BMC",
		"Subsystem": "BMC Firmware",
		"Commit Time": "12/05/2019 18:32:42",
		"Sev": "Informational Event",
 		"CompID": "0x1000"
 	}
}

\# ./peltool -l -h -s filename
{}

\# ./peltool -n -h -s filename
{
    "Number of PELs found": 0
}

Signed-off-by: Harisuddin Mohamed Isa <harisuddin@gmail.com>
Change-Id: Idb44c6574f1bd192dfbf354d2879cae084251e4c
diff --git a/extensions/openpower-pels/tools/peltool.cpp b/extensions/openpower-pels/tools/peltool.cpp
index 142a712..7d1e304 100644
--- a/extensions/openpower-pels/tools/peltool.cpp
+++ b/extensions/openpower-pels/tools/peltool.cpp
@@ -24,6 +24,7 @@
 
 #include <CLI/CLI.hpp>
 #include <bitset>
+#include <fstream>
 #include <iostream>
 #include <phosphor-logging/log.hpp>
 #include <regex>
@@ -210,10 +211,12 @@
  * @param[in] hidden - Boolean to include hidden PELs
  * @param[in] fullPEL - Boolean to print full JSON representation of PEL
  * @param[in] foundPEL - Boolean to check if any PEL is present
+ * @param[in] scrubRegex - SRC regex object
  * @return std::string - JSON string of PEL entry (empty if fullPEL is true)
  */
 template <typename T>
-std::string genPELJSON(T itr, bool hidden, bool fullPEL, bool& foundPEL)
+std::string genPELJSON(T itr, bool hidden, bool fullPEL, bool& foundPEL,
+                       const std::optional<std::regex>& scrubRegex)
 {
     std::size_t found;
     std::string val;
@@ -236,90 +239,102 @@
             return listStr;
         }
         PEL pel{data};
-        std::bitset<16> actionFlags{pel.userHeader().actionFlags()};
-        if (pel.valid() && (hidden || !actionFlags.test(hiddenFlagBit)))
+        if (!pel.valid())
         {
-            if (fullPEL)
+            return listStr;
+        }
+        std::bitset<16> actionFlags{pel.userHeader().actionFlags()};
+        if (!hidden && actionFlags.test(hiddenFlagBit))
+        {
+            return listStr;
+        }
+        if (pel.primarySRC() && scrubRegex)
+        {
+            val = pel.primarySRC().value()->asciiString();
+            if (std::regex_search(trimEnd(val), scrubRegex.value(),
+                                  std::regex_constants::match_not_null))
             {
-                if (!foundPEL)
-                {
-                    std::cout << "[" << std::endl;
-                    foundPEL = true;
-                }
-                else
-                {
-                    std::cout << ",\n" << std::endl;
-                }
-                pel.toJSON(registry);
+                return listStr;
+            }
+        }
+        if (fullPEL)
+        {
+            if (!foundPEL)
+            {
+                std::cout << "[\n";
+                foundPEL = true;
             }
             else
             {
-                // id
-                sprintf(tmpValStr, "0x%X", pel.privateHeader().id());
-                val = std::string(tmpValStr);
-                listStr += "\t\"" + val + "\": {\n";
-                // ASCII
-                if (pel.primarySRC())
+                std::cout << ",\n\n";
+            }
+            pel.toJSON(registry);
+        }
+        else
+        {
+            // id
+            listStr += "\t\"" +
+                       getNumberString("0x%X", pel.privateHeader().id()) +
+                       "\": {\n";
+            // ASCII
+            if (pel.primarySRC())
+            {
+                val = pel.primarySRC().value()->asciiString();
+                listStr += "\t\t\"SRC\": \"" + trimEnd(val) + "\",\n";
+                // Registry message
+                auto regVal = pel.primarySRC().value()->getErrorDetails(
+                    registry, DetailLevel::message, true);
+                if (regVal)
                 {
-                    val = pel.primarySRC().value()->asciiString();
-                    listStr += "\t\t\"SRC\": \"" +
-                               val.substr(0, val.find(0x20)) + "\",\n";
-                    // Registry message
-                    auto regVal = pel.primarySRC().value()->getErrorDetails(
-                        registry, DetailLevel::message, true);
-                    if (regVal)
-                    {
-                        val = regVal.value();
-                        listStr += "\t\t\"Message\": \"" + val + "\",\n";
-                    }
-                }
-                else
-                {
-                    listStr += "\t\t\"SRC\": \"No SRC\",\n";
-                }
-                // platformid
-                sprintf(tmpValStr, "0x%X", pel.privateHeader().plid());
-                val = std::string(tmpValStr);
-                listStr += "\t\t\"PLID\": \"" + val + "\",\n";
-                // creatorid
-                sprintf(tmpValStr, "%c", pel.privateHeader().creatorID());
-                std::string creatorID(tmpValStr);
-                val = pv::creatorIDs.count(creatorID)
-                          ? pv::creatorIDs.at(creatorID)
-                          : "Unknown Creator ID";
-                listStr += "\t\t\"CreatorID\": \"" + val + "\",\n";
-                // subsytem
-                std::string subsystem = pv::getValue(
-                    pel.userHeader().subsystem(), pel_values::subsystemValues);
-                listStr += "\t\t\"Subsystem\": \"" + subsystem + "\",\n";
-                // commit time
-                sprintf(tmpValStr, "%02X/%02X/%02X%02X %02X:%02X:%02X",
-                        pel.privateHeader().commitTimestamp().month,
-                        pel.privateHeader().commitTimestamp().day,
-                        pel.privateHeader().commitTimestamp().yearMSB,
-                        pel.privateHeader().commitTimestamp().yearLSB,
-                        pel.privateHeader().commitTimestamp().hour,
-                        pel.privateHeader().commitTimestamp().minutes,
-                        pel.privateHeader().commitTimestamp().seconds);
-                val = std::string(tmpValStr);
-                listStr += "\t\t\"Commit Time\": \"" + val + "\",\n";
-                // severity
-                std::string severity = pv::getValue(pel.userHeader().severity(),
-                                                    pel_values::severityValues);
-                listStr += "\t\t\"Sev\": \"" + severity + "\",\n ";
-                // compID
-                sprintf(tmpValStr, "0x%X",
-                        pel.privateHeader().header().componentID);
-                val = std::string(tmpValStr);
-                listStr += "\t\t\"CompID\": \"" + val + "\",\n ";
-
-                found = listStr.rfind(",");
-                if (found != std::string::npos)
-                {
-                    listStr.replace(found, 1, "");
-                    listStr += "\t}, \n";
+                    val = regVal.value();
+                    listStr += "\t\t\"Message\": \"" + val + "\",\n";
                 }
             }
+            else
+            {
+                listStr += "\t\t\"SRC\": \"No SRC\",\n";
+            }
+            // platformid
+            listStr += "\t\t\"PLID\": \"" +
+                       getNumberString("0x%X", pel.privateHeader().plid()) +
+                       "\",\n";
+            // creatorid
+            std::string creatorID =
+                getNumberString("%c", pel.privateHeader().creatorID());
+            val = pv::creatorIDs.count(creatorID) ? pv::creatorIDs.at(creatorID)
+                                                  : "Unknown Creator ID";
+            listStr += "\t\t\"CreatorID\": \"" + val + "\",\n";
+            // subsytem
+            std::string subsystem = pv::getValue(pel.userHeader().subsystem(),
+                                                 pel_values::subsystemValues);
+            listStr += "\t\t\"Subsystem\": \"" + subsystem + "\",\n";
+            // commit time
+            sprintf(tmpValStr, "%02X/%02X/%02X%02X %02X:%02X:%02X",
+                    pel.privateHeader().commitTimestamp().month,
+                    pel.privateHeader().commitTimestamp().day,
+                    pel.privateHeader().commitTimestamp().yearMSB,
+                    pel.privateHeader().commitTimestamp().yearLSB,
+                    pel.privateHeader().commitTimestamp().hour,
+                    pel.privateHeader().commitTimestamp().minutes,
+                    pel.privateHeader().commitTimestamp().seconds);
+            val = std::string(tmpValStr);
+            listStr += "\t\t\"Commit Time\": \"" + val + "\",\n";
+            // severity
+            std::string severity = pv::getValue(pel.userHeader().severity(),
+                                                pel_values::severityValues);
+            listStr += "\t\t\"Sev\": \"" + severity + "\",\n ";
+            // compID
+            listStr += "\t\t\"CompID\": \"" +
+                       getNumberString(
+                           "0x%X", pel.privateHeader().header().componentID) +
+                       "\",\n ";
+            found = listStr.rfind(",");
+            if (found != std::string::npos)
+            {
+                listStr.replace(found, 1, "");
+                listStr += "\t},\n";
+            }
+            foundPEL = true;
         }
     }
     catch (std::exception& e)
@@ -336,8 +351,10 @@
  * @param[in] order - Boolean to print in reverse orser
  * @param[in] hidden - Boolean to include hidden PELs
  * @param[in] fullPEL - Boolean to print full PEL into a JSON array
+ * @param[in] scrubRegex - SRC regex object
  */
-void printPELs(bool order, bool hidden, bool fullPEL)
+void printPELs(bool order, bool hidden, bool fullPEL,
+               const std::optional<std::regex>& scrubRegex)
 {
     std::string listStr;
     std::map<uint32_t, BCDTime> PELs;
@@ -356,8 +373,9 @@
         }
     }
     bool foundPEL = false;
-    auto buildJSON = [&listStr, &hidden, &fullPEL, &foundPEL](const auto& i) {
-        listStr += genPELJSON(i, hidden, fullPEL, foundPEL);
+    auto buildJSON = [&listStr, &hidden, &fullPEL, &foundPEL,
+                      &scrubRegex](const auto& i) {
+        listStr += genPELJSON(i, hidden, fullPEL, foundPEL, scrubRegex);
     };
     if (order)
     {
@@ -368,20 +386,28 @@
         std::for_each(PELs.begin(), PELs.end(), buildJSON);
     }
 
-    if (!fullPEL)
+    if (foundPEL)
     {
-        std::size_t found;
-        found = listStr.rfind(",");
-        if (found != std::string::npos)
+        if (fullPEL)
         {
-            listStr.replace(found, 1, "");
-            listStr += "\n}\n";
-            printf("%s", listStr.c_str());
+            std::cout << "]" << std::endl;
+        }
+        else
+        {
+            std::size_t found;
+            found = listStr.rfind(",");
+            if (found != std::string::npos)
+            {
+                listStr.replace(found, 1, "");
+                listStr += "}\n";
+                printf("%s", listStr.c_str());
+            }
         }
     }
-    else if (foundPEL)
+    else
     {
-        std::cout << "]" << std::endl;
+        std::string emptyJSON = fullPEL ? "[]" : "{}";
+        std::cout << emptyJSON << std::endl;
     }
 }
 
@@ -522,8 +548,9 @@
 /**
  * @brief Print number of PELs
  * @param[in] hidden - Bool to include hidden logs
+ * @param[in] scrubRegex - SRC regex object
  */
-void printPELCount(bool hidden)
+void printPELCount(bool hidden, const std::optional<std::regex>& scrubRegex)
 {
     std::size_t count = 0;
     for (auto it = fs::directory_iterator(EXTENSION_PERSIST_DIR "/pels/logs");
@@ -533,23 +560,111 @@
         {
             continue;
         }
-        else
+        std::vector<uint8_t> data = getFileData((*it).path());
+        if (data.empty())
         {
-            std::vector<uint8_t> data = getFileData((*it).path());
-            if (!data.empty())
+            continue;
+        }
+        PEL pel{data};
+        if (!pel.valid())
+        {
+            continue;
+        }
+        std::bitset<16> actionFlags{pel.userHeader().actionFlags()};
+        if (!hidden && actionFlags.test(hiddenFlagBit))
+        {
+            continue;
+        }
+        if (pel.primarySRC() && scrubRegex)
+        {
+            std::string val = pel.primarySRC().value()->asciiString();
+            if (std::regex_search(trimEnd(val), scrubRegex.value(),
+                                  std::regex_constants::match_not_null))
             {
-                PEL pel{data};
-                std::bitset<16> actionFlags{pel.userHeader().actionFlags()};
-                if (pel.valid() && (hidden || !actionFlags.test(hiddenFlagBit)))
-                {
-                    count++;
-                }
+                continue;
             }
         }
+        count++;
     }
     std::cout << "{\n"
               << "    \"Number of PELs found\": "
-              << getNumberString("%d", count) << "\n}" << std::endl;
+              << getNumberString("%d", count) << "\n}\n";
+}
+
+/**
+ * @brief Generate regex pattern object from file contents
+ * @param[in] scrubFile - File containing regex pattern
+ * @return std::regex - SRC regex object
+ */
+std::regex genRegex(std::string& scrubFile)
+{
+    std::string pattern;
+    std::ifstream contents(scrubFile);
+    if (contents.fail())
+    {
+        std::cerr << "Can't open \"" << scrubFile << "\"\n";
+        exit(1);
+    }
+    std::string line;
+    while (std::getline(contents, line))
+    {
+        if (!line.empty())
+        {
+            pattern.append(line + "|");
+        }
+    }
+    try
+    {
+        std::regex scrubRegex(pattern, std::regex::icase);
+        return scrubRegex;
+    }
+    catch (std::regex_error& e)
+    {
+        if (e.code() == std::regex_constants::error_collate)
+            std::cerr << "Invalid collating element request\n";
+        else if (e.code() == std::regex_constants::error_ctype)
+            std::cerr << "Invalid character class\n";
+        else if (e.code() == std::regex_constants::error_escape)
+            std::cerr << "Invalid escape character or trailing escape\n";
+        else if (e.code() == std::regex_constants::error_backref)
+            std::cerr << "Invalid back reference\n";
+        else if (e.code() == std::regex_constants::error_brack)
+            std::cerr << "Mismatched bracket ([ or ])\n";
+        else if (e.code() == std::regex_constants::error_paren)
+        {
+            // to catch return code error_badrepeat when error_paren is retured
+            // instead
+            size_t pos = pattern.find_first_of("*+?{");
+            while (pos != std::string::npos)
+            {
+                if (pos == 0 || pattern.substr(pos - 1, 1) == "|")
+                {
+                    std::cerr
+                        << "A repetition character (*, ?, +, or {) was not "
+                           "preceded by a valid regular expression\n";
+                    exit(1);
+                }
+                pos = pattern.find_first_of("*+?{", pos + 1);
+            }
+            std::cerr << "Mismatched parentheses (( or ))\n";
+        }
+        else if (e.code() == std::regex_constants::error_brace)
+            std::cerr << "Mismatched brace ({ or })\n";
+        else if (e.code() == std::regex_constants::error_badbrace)
+            std::cerr << "Invalid range inside a { }\n";
+        else if (e.code() == std::regex_constants::error_range)
+            std::cerr << "Invalid character range (e.g., [z-a])\n";
+        else if (e.code() == std::regex_constants::error_space)
+            std::cerr << "Insufficient memory to handle regular expression\n";
+        else if (e.code() == std::regex_constants::error_badrepeat)
+            std::cerr << "A repetition character (*, ?, +, or {) was not "
+                         "preceded by a valid regular expression\n";
+        else if (e.code() == std::regex_constants::error_complexity)
+            std::cerr << "The requested match is too complex\n";
+        else if (e.code() == std::regex_constants::error_stack)
+            std::cerr << "Insufficient memory to evaluate a match\n";
+        exit(1);
+    }
 }
 
 static void exitWithError(const std::string& help, const char* err)
@@ -564,6 +679,8 @@
     std::string fileName;
     std::string idPEL;
     std::string idToDelete;
+    std::string scrubFile;
+    std::optional<std::regex> scrubRegex;
     bool listPEL = false;
     bool listPELDescOrd = false;
     bool hidden = false;
@@ -582,6 +699,8 @@
     app.add_flag("-h", hidden, "Include hidden PELs");
     app.add_option("-d, --delete", idToDelete, "Delete a PEL based on its ID");
     app.add_flag("-D, --delete-all", deleteAll, "Delete all PELs");
+    app.add_option("-s, --scrub", scrubFile,
+                   "File containing SRC regular expressions to ignore");
 
     CLI11_PARSE(app, argc, argv);
 
@@ -605,11 +724,19 @@
     }
     else if (fullPEL || listPEL)
     {
-        printPELs(listPELDescOrd, hidden, fullPEL);
+        if (!scrubFile.empty())
+        {
+            scrubRegex = genRegex(scrubFile);
+        }
+        printPELs(listPELDescOrd, hidden, fullPEL, scrubRegex);
     }
     else if (showPELCount)
     {
-        printPELCount(hidden);
+        if (!scrubFile.empty())
+        {
+            scrubRegex = genRegex(scrubFile);
+        }
+        printPELCount(hidden, scrubRegex);
     }
     else if (!idToDelete.empty())
     {