Add support for post code handling/eventing

Add support to consume a JSON configuration which
will let a platform configuration to dictate if
we need to do special handling or logging based
on specific post codes received.

Tested:

1. Created the following config and after restarting
`xyz.openbmc_project.State.Boot.PostCode@0.service`:
```
[
    {
        "primary": [1,2,5],
        "event": {
            "name": "xyz.openbmc_project.State.SMC.SMCFailed",
            "arguments": {
                "IDENTIFIER": "test",
                "FAILURE_TYPE": "world"
            }
        }
    }
]
```

Created the primary post code:
```
root@bmc:~# busctl set-property xyz.openbmc_project.State.Boot.Raw /xyz/openbmc_project/state/boot/raw0 xyz.openbmc_project.State.Boot.Raw Value '(ayay)' 3 0x1 0x2 0x5 0x0
```
Check that the log is created:
```
root@bmc:~# busctl introspect -l xyz.openbmc_project.Logging /xyz/openbmc_project/logging/entry/2 xyz.openbmc_project.Logging.Entry | grep "AdditionalData\|Message"
.AdditionalData property  a{ss}     6 "FAILURE_TYPE" "world" "IDENTIFIER" "test" <snip>
.Message property  s "xyz.openbmc_project.State.SMC.SMCFailed"
```
2. Replace the config with:
```
[
    {
        "primary": [1,2,6],
        "targets": ["testservice.service"]
    }
]
```
Create a test unit file:
testservice.service:
```
[Unit]
Description=A simple oneshot service

[Service]
Type=oneshot
ExecStart=/bin/bash -c "echo Hello world > /tmp/test.txt"
```
Issue a new post code with the new config.
```
systemctl daemon-reload
systemctl restart xyz.openbmc_project.State.Boot.PostCode@0.service
root@bmc:/usr/lib/systemd/system# busctl set-property xyz.openbmc_project.State.Boot.Raw /xyz/openbmc_project/state/boot/raw0 xyz.openbmc_project.State.Boot.Raw Value '(ayay)' 3 0x1 0x2 0x5 0x0
root@bmc:/usr/lib/systemd/system# cat /tmp/test.txt
Hello world
```

Change-Id: Ibaa09fb88190a344fd7c4fadd24dda4aa4ae0633
Signed-off-by: Amithash Prasad <amithash@meta.com>
diff --git a/README.md b/README.md
index ab1b8c7..03df025 100644
--- a/README.md
+++ b/README.md
@@ -35,3 +35,48 @@
 property on `xyz.openbmc_project.State.Boot.Raw` interface & archive them per
 boot on the filesystem, so that those can be exposed over
 [redfish](https://github.com/openbmc/docs/blob/master/designs/redfish-postcodes.md)
+
+## Special Post code handling/eventing
+
+Platforms can provide custom configuration to allow for special handling of
+specific postcodes. Special handling include starting user configured systemd
+unit files and/or creating a structured event as defined by the user. The JSON
+configuration file can be passed in using the `--config PATH` or `-c PATH`
+options.
+
+Configuration format:
+
+```json
+[
+  {
+    "primary": [123],
+    "secondary": [234, 123],
+    "targets": ["my_special.service"]
+  },
+  {
+    "primary": [999],
+    "targets": ["power_failure.service"],
+    "event": {
+      "name": "xyz.openbmc_project.State.Power.PowerRailFault",
+      "arguments": {
+        "POWER_RAIL": "MyDevice",
+        "FAILURE_DATA": ""
+      }
+    }
+  }
+]
+```
+
+Each entry in the array describes a special handler for a specific post code.
+
+- `primary` - [required] The primary post code to match.
+- `secondary` - [optional] The secondary post code to match. If not present, the
+  match will be for all post codes which match just the primary
+- `targets` - [optional] List of systemd targets to start when the matching post
+  code is received.
+- `event` - [optional] The descriptor of the event to create with
+  phosphor-logging.
+  - `event::name` - Name of the event log. See `log-create --list` for a list of
+    possible events which can be created
+  - `event::arguments` - The named argument list which will be used to create
+    the event.
diff --git a/inc/post_code.hpp b/inc/post_code.hpp
index 9127cbf..262a773 100644
--- a/inc/post_code.hpp
+++ b/inc/post_code.hpp
@@ -18,6 +18,7 @@
 #include <fcntl.h>
 #include <unistd.h>
 
+#include <nlohmann/json.hpp>
 #include <phosphor-logging/elog-errors.hpp>
 #include <sdbusplus/timer.hpp>
 #include <xyz/openbmc_project/Collection/DeleteAll/server.hpp>
@@ -61,10 +62,33 @@
 using delete_all =
     sdbusplus::xyz::openbmc_project::Collection::server::DeleteAll;
 
+struct PostCodeEvent
+{
+    std::string name;
+    nlohmann::json args;
+    void raise() const;
+};
+
+struct PostCodeHandler
+{
+    primarycode_t primary;
+    std::optional<secondarycode_t> secondary;
+    std::vector<std::string> targets;
+    std::optional<PostCodeEvent> event;
+};
+
+struct PostCodeHandlers
+{
+    std::vector<PostCodeHandler> handlers;
+    void handle(postcode_t code);
+    const PostCodeHandler* find(postcode_t code);
+    void load(const std::string& path);
+};
+
 struct PostCode : sdbusplus::server::object_t<post_code, delete_all>
 {
     PostCode(sdbusplus::bus_t& bus, const char* path, EventPtr& event,
-             int nodeIndex) :
+             int nodeIndex, PostCodeHandlers& handlers) :
         sdbusplus::server::object_t<post_code, delete_all>(bus, path), bus(bus),
         event(event), node(nodeIndex),
         postCodeListPath(PostCodeListPathPrefix + std::to_string(node)),
@@ -116,7 +140,8 @@
                         }
                     }
                 }
-            })
+            }),
+        postCodeHandlers(std::move(handlers))
     {
         phosphor::logging::log<phosphor::logging::level::INFO>(
             "PostCode is created");
@@ -156,4 +181,5 @@
     bool deserialize(const fs::path& path, uint16_t& index);
     bool deserializePostCodes(const fs::path& path,
                               std::map<uint64_t, postcode_t>& codes);
+    PostCodeHandlers postCodeHandlers;
 };
diff --git a/meson.build b/meson.build
index c7e9e16..dbb94d6 100644
--- a/meson.build
+++ b/meson.build
@@ -7,6 +7,8 @@
     version: '1.0',
 )
 
+# Disable JSON implicit conversions.
+add_project_arguments('-DJSON_USE_IMPLICIT_CONVERSIONS=0', language: 'cpp')
 
 conf_data = configuration_data()
 conf_data.set_quoted(
@@ -33,6 +35,7 @@
 sdbusplus = dependency('sdbusplus')
 phosphor_logging = dependency('phosphor-logging')
 phosphor_dbus_interfaces = dependency('phosphor-dbus-interfaces')
+json = dependency('nlohmann_json', include_type: 'system')
 
 cxx = meson.get_compiler('cpp')
 cereal_dep = dependency('cereal', required: false)
@@ -66,6 +69,14 @@
     strip_directory: true,
 )
 
+packagedir = join_paths(
+    get_option('prefix'),
+    get_option('datadir'),
+    'phosphor-post-code-manager',
+)
+
+install_data(sources: 'post-code-handlers.json', install_dir: packagedir)
+
 executable(
     'post-code-manager',
     'src/main.cpp',
@@ -76,6 +87,7 @@
         phosphor_dbus_interfaces,
         phosphor_logging,
         cereal_dep,
+        json,
     ],
     include_directories: 'inc',
 )
diff --git a/post-code-handlers.json b/post-code-handlers.json
new file mode 100644
index 0000000..fe51488
--- /dev/null
+++ b/post-code-handlers.json
@@ -0,0 +1 @@
+[]
diff --git a/service_files/xyz.openbmc_project.State.Boot.PostCode.service b/service_files/xyz.openbmc_project.State.Boot.PostCode.service
index 35ffd1f..325922f 100644
--- a/service_files/xyz.openbmc_project.State.Boot.PostCode.service
+++ b/service_files/xyz.openbmc_project.State.Boot.PostCode.service
@@ -2,7 +2,7 @@
 Description=Post code manager
 
 [Service]
-ExecStart=/usr/bin/post-code-manager --host 0
+ExecStart=/usr/bin/post-code-manager --host 0 --config /usr/share/phosphor-post-code-manager/post-code-handlers.json
 SyslogIdentifier=post-code-manager
 Type=dbus
 BusName=xyz.openbmc_project.State.Boot.PostCode0
diff --git a/service_files/xyz.openbmc_project.State.Boot.PostCode@.service b/service_files/xyz.openbmc_project.State.Boot.PostCode@.service
index db21c21..d4e8274 100644
--- a/service_files/xyz.openbmc_project.State.Boot.PostCode@.service
+++ b/service_files/xyz.openbmc_project.State.Boot.PostCode@.service
@@ -2,7 +2,7 @@
 Description=Post code manager (host %i)
 
 [Service]
-ExecStart=/usr/bin/env post-code-manager --host %i
+ExecStart=/usr/bin/env post-code-manager --host %i --config /usr/share/phosphor-post-code-manager/post-code-handlers.json
 SyslogIdentifier=post-code-manager%i
 Type=dbus
 BusName=xyz.openbmc_project.State.Boot.PostCode%i
diff --git a/src/main.cpp b/src/main.cpp
index be4b6b9..6ed2e55 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -23,19 +23,24 @@
     int optIndex = 0;
     int ret = 0;
     int node = 0;
+    PostCodeHandlers handlers;
 
     std::string intfName;
 
     static struct option longOpts[] = {{"host", required_argument, 0, 'h'},
+                                       {"config", required_argument, 0, 'c'},
                                        {0, 0, 0, 0}};
 
-    while ((arg = getopt_long(argc, argv, "h:", longOpts, &optIndex)) != -1)
+    while ((arg = getopt_long(argc, argv, "h:c:", longOpts, &optIndex)) != -1)
     {
         switch (arg)
         {
             case 'h':
                 node = std::stoi(optarg);
                 break;
+            case 'c':
+                handlers.load(optarg);
+                break;
             default:
                 break;
         }
@@ -64,7 +69,7 @@
 
     bus.request_name(intfName.c_str());
 
-    PostCode postCode{bus, dbusObjName.c_str(), eventP, node};
+    PostCode postCode{bus, dbusObjName.c_str(), eventP, node, handlers};
 
     try
     {
diff --git a/src/post_code.cpp b/src/post_code.cpp
index 82791c2..edad39d 100644
--- a/src/post_code.cpp
+++ b/src/post_code.cpp
@@ -21,10 +21,112 @@
 #include <cereal/types/map.hpp>
 #include <cereal/types/tuple.hpp>
 #include <cereal/types/vector.hpp>
+#include <phosphor-logging/commit.hpp>
+#include <sdbusplus/bus.hpp>
+#include <sdbusplus/exception.hpp>
 
 #include <iomanip>
 
+using nlohmann::json;
+
 const static constexpr auto timeoutMicroSeconds = 1000000;
+/* systemd service to kick start a target. */
+constexpr auto SYSTEMD_SERVICE = "org.freedesktop.systemd1";
+constexpr auto SYSTEMD_ROOT = "/org/freedesktop/systemd1";
+constexpr auto SYSTEMD_INTERFACE = "org.freedesktop.systemd1.Manager";
+
+void PostCodeEvent::raise() const
+{
+    json j = {{name, args}};
+    try
+    {
+        sdbusplus::exception::throw_via_json(j);
+    }
+    catch (sdbusplus::exception::generated_event_base& e)
+    {
+        auto path = lg2::commit(std::move(e));
+        std::cout << path.str << std::endl;
+    }
+}
+
+void from_json(const json& j, PostCodeEvent& event)
+{
+    j.at("name").get_to(event.name);
+    for (const auto& entry : j.at("arguments").items())
+    {
+        if (entry.value().is_string())
+        {
+            event.args[entry.key()] = entry.value().get<std::string>();
+        }
+        else if (entry.value().is_number_integer())
+        {
+            event.args[entry.key()] = entry.value().get<int>();
+        }
+    }
+}
+
+void from_json(const json& j, PostCodeHandler& handler)
+{
+    j.at("primary").get_to(handler.primary);
+    if (j.contains("secondary"))
+    {
+        secondarycode_t secondary;
+        j.at("secondary").get_to(secondary);
+        handler.secondary = secondary;
+    }
+    if (j.contains("targets"))
+    {
+        j.at("targets").get_to(handler.targets);
+    }
+    if (j.contains("event"))
+    {
+        PostCodeEvent event;
+        j.at("event").get_to(event);
+        handler.event = event;
+    }
+}
+
+const PostCodeHandler* PostCodeHandlers::find(postcode_t code)
+{
+    for (const auto& handler : handlers)
+    {
+        if (handler.primary == std::get<0>(code) &&
+            (!handler.secondary || *handler.secondary == std::get<1>(code)))
+        {
+            return &handler;
+        }
+    }
+    return nullptr;
+}
+
+void PostCodeHandlers::handle(postcode_t code)
+{
+    const PostCodeHandler* handler = find(code);
+    if (!handler)
+    {
+        return;
+    }
+    for (const auto& target : handler->targets)
+    {
+        auto bus = sdbusplus::bus::new_default();
+        auto method = bus.new_method_call(SYSTEMD_SERVICE, SYSTEMD_ROOT,
+                                          SYSTEMD_INTERFACE, "StartUnit");
+        method.append(target);
+        method.append("replace");
+        bus.call_noreply(method);
+    }
+    if (handler->event)
+    {
+        (*(handler->event)).raise();
+    }
+}
+
+void PostCodeHandlers::load(const std::string& path)
+{
+    std::ifstream ifs(path);
+    handlers = json::parse(ifs).template get<std::vector<PostCodeHandler>>();
+    ifs.close();
+}
 
 void PostCode::deleteAll()
 {
@@ -152,6 +254,7 @@
             "REDFISH_MESSAGE_ARGS=%d,%s,%s", currentBootCycleIndex,
             timeOffsetStr.str().c_str(), hexCode.str().c_str()));
 #endif
+    postCodeHandlers.handle(code);
 
     return;
 }