ncsi: return interface info from Interface::getInfo()

Rather than expecting the ncsi_util callback to print the interface
info, return the interface info, and print that.

This allows for a specific output formatting function in
ncsi_netlink_main.c, rather than printing directly from the libnl
callback. We reformat for a more structured display of the
package/channel layouts.

We use std::optional for the return value here; it would be nice to use
std::expected instead, and get a full error code, but that's not quite
working with clang-18 at present. Even if we have an error code though,
we're just going to return EXIT_FAILURE anyway.

Tested: Invoked on a simlated dual-channel NCSI package:

    # ncsi-netlink -x 2 --info
    <7> Get Info , PACKAGE : 0xffffffffffffffff, INTERFACE: 0x7e8bf9bc
    <7> Package id : 0
    <7>   package is forced
    <7>     Channel id : 0
    <7>       version 1.2 (p0c00)
    <7>       link state 0x40022f
    <7>     Channel id : 1
    <7>       version 1.2 (p0c01)
    <7>       link state 0x40022f

Change-Id: Idb62cc6695da67f4415ed9b0e7950c506018d630
Signed-off-by: Jeremy Kerr <jk@codeconstruct.com.au>
diff --git a/src/ncsi_util.cpp b/src/ncsi_util.cpp
index c755d33..8c628aa 100644
--- a/src/ncsi_util.cpp
+++ b/src/ncsi_util.cpp
@@ -78,7 +78,19 @@
 using nlMsgPtr = std::unique_ptr<nl_msg, decltype(&::nlmsg_free)>;
 using nlSocketPtr = std::unique_ptr<nl_sock, decltype(&::nl_socket_free)>;
 
-CallBack infoCallBack = [](struct nl_msg* msg, void*) {
+struct infoCallBackContext
+{
+    InterfaceInfo* info;
+};
+
+CallBack infoCallBack = [](struct nl_msg* msg, void* arg) {
+    if (arg == nullptr)
+    {
+        lg2::error("Internal error: invalid info callback context");
+        return -1;
+    }
+
+    struct infoCallBackContext* info = (struct infoCallBackContext*)arg;
     using namespace phosphor::network::ncsi;
     auto nlh = nlmsg_hdr(msg);
 
@@ -125,11 +137,12 @@
             return -1;
         }
 
+        PackageInfo pkg;
+
         if (packagetb[NCSI_PKG_ATTR_ID])
         {
             auto attrID = nla_get_u32(packagetb[NCSI_PKG_ATTR_ID]);
-            lg2::debug("Package has id : {ATTR_ID}", "ATTR_ID", lg2::hex,
-                       attrID);
+            pkg.id = attrID;
         }
         else
         {
@@ -138,7 +151,7 @@
 
         if (packagetb[NCSI_PKG_ATTR_FORCED])
         {
-            lg2::debug("This package is forced");
+            pkg.forced = true;
         }
 
         auto channelListTarget = static_cast<nlattr*>(
@@ -153,75 +166,59 @@
             if (ret < 0)
             {
                 lg2::error("Failed to parse channel nested");
-                return -1;
+                continue;
             }
 
+            ChannelInfo chan;
+
             if (channeltb[NCSI_CHANNEL_ATTR_ID])
             {
-                auto channel = nla_get_u32(channeltb[NCSI_CHANNEL_ATTR_ID]);
-                if (channeltb[NCSI_CHANNEL_ATTR_ACTIVE])
-                {
-                    lg2::debug("Channel Active : {CHANNEL}", "CHANNEL",
-                               lg2::hex, channel);
-                }
-                else
-                {
-                    lg2::debug("Channel Not Active : {CHANNEL}", "CHANNEL",
-                               lg2::hex, channel);
-                }
-
-                if (channeltb[NCSI_CHANNEL_ATTR_FORCED])
-                {
-                    lg2::debug("Channel is forced");
-                }
+                chan.id = nla_get_u32(channeltb[NCSI_CHANNEL_ATTR_ID]);
+                chan.active = !!channeltb[NCSI_CHANNEL_ATTR_ACTIVE];
+                chan.forced = !!channeltb[NCSI_CHANNEL_ATTR_FORCED];
             }
             else
             {
                 lg2::debug("Channel with no ID");
+                continue;
             }
 
             if (channeltb[NCSI_CHANNEL_ATTR_VERSION_MAJOR])
             {
-                auto major =
+                chan.version_major =
                     nla_get_u32(channeltb[NCSI_CHANNEL_ATTR_VERSION_MAJOR]);
-                lg2::debug("Channel Major Version : {CHANNEL_MAJOR_VERSION}",
-                           "CHANNEL_MAJOR_VERSION", lg2::hex, major);
             }
             if (channeltb[NCSI_CHANNEL_ATTR_VERSION_MINOR])
             {
-                auto minor =
+                chan.version_minor =
                     nla_get_u32(channeltb[NCSI_CHANNEL_ATTR_VERSION_MINOR]);
-                lg2::debug("Channel Minor Version : {CHANNEL_MINOR_VERSION}",
-                           "CHANNEL_MINOR_VERSION", lg2::hex, minor);
             }
             if (channeltb[NCSI_CHANNEL_ATTR_VERSION_STR])
             {
-                auto str =
+                chan.version =
                     nla_get_string(channeltb[NCSI_CHANNEL_ATTR_VERSION_STR]);
-                lg2::debug("Channel Version Str : {CHANNEL_VERSION_STR}",
-                           "CHANNEL_VERSION_STR", str);
             }
             if (channeltb[NCSI_CHANNEL_ATTR_LINK_STATE])
             {
-                auto link =
+                chan.link_state =
                     nla_get_u32(channeltb[NCSI_CHANNEL_ATTR_LINK_STATE]);
-                lg2::debug("Channel Link State : {LINK_STATE}", "LINK_STATE",
-                           lg2::hex, link);
             }
             if (channeltb[NCSI_CHANNEL_ATTR_VLAN_LIST])
             {
-                lg2::debug("Active Vlan ids");
                 auto vids = channeltb[NCSI_CHANNEL_ATTR_VLAN_LIST];
                 auto vid = static_cast<nlattr*>(nla_data(vids));
                 auto len = nla_len(vids);
                 while (nla_ok(vid, len))
                 {
                     auto id = nla_get_u16(vid);
-                    lg2::debug("VID : {VLAN_ID}", "VLAN_ID", id);
+                    chan.vlan_ids.push_back(id);
                     vid = nla_next(vid, &len);
                 }
             }
+            pkg.channels.push_back(chan);
         }
+
+        info->info->packages.push_back(pkg);
     }
     return static_cast<int>(NL_STOP);
 };
@@ -263,7 +260,7 @@
 
 int applyCmd(Interface& interface, const Command& cmd,
              int package = DEFAULT_VALUE, int channel = DEFAULT_VALUE,
-             int flags = NONE, CallBack function = nullptr)
+             int flags = NONE, CallBack function = nullptr, void* arg = nullptr)
 {
     nlSocketPtr socket(nl_socket_alloc(), &::nl_socket_free);
     if (socket == nullptr)
@@ -388,7 +385,7 @@
 
     // Add a callback function to the socket
     enum nl_cb_kind cb_kind = function ? NL_CB_CUSTOM : NL_CB_DEFAULT;
-    nl_socket_modify_cb(socket.get(), NL_CB_VALID, cb_kind, function, nullptr);
+    nl_socket_modify_cb(socket.get(), NL_CB_VALID, cb_kind, function, arg);
 
     ret = nl_send_auto(socket.get(), msg.get());
     if (ret < 0)
@@ -451,22 +448,28 @@
         *this, internal::Command(ncsi_nl_commands::NCSI_CMD_CLEAR_INTERFACE));
 }
 
-int Interface::getInfo(int package)
+std::optional<InterfaceInfo> Interface::getInfo(int package)
 {
+    int rc, flags = package == DEFAULT_VALUE ? NLM_F_DUMP : NONE;
+    InterfaceInfo info;
+
     lg2::debug("Get Info , PACKAGE : {PACKAGE}, INTERFACE: {INTERFACE}",
                "PACKAGE", lg2::hex, package, "INTERFACE", this);
-    if (package == DEFAULT_VALUE)
+
+    struct internal::infoCallBackContext ctx = {
+        .info = &info,
+    };
+
+    rc = internal::applyCmd(
+        *this, internal::Command(ncsi_nl_commands::NCSI_CMD_PKG_INFO), package,
+        DEFAULT_VALUE, flags, internal::infoCallBack, &ctx);
+
+    if (rc < 0)
     {
-        return internal::applyCmd(
-            *this, internal::Command(ncsi_nl_commands::NCSI_CMD_PKG_INFO),
-            package, DEFAULT_VALUE, NLM_F_DUMP, internal::infoCallBack);
+        return {};
     }
-    else
-    {
-        return internal::applyCmd(*this, ncsi_nl_commands::NCSI_CMD_PKG_INFO,
-                                  package, DEFAULT_VALUE, NONE,
-                                  internal::infoCallBack);
-    }
+
+    return info;
 }
 
 int Interface::setPackageMask(unsigned int mask)