ncsi-cmd: Add a new executable for issuing NCSI commands

At the moment, the ncsi-netlink utility does two things:

 - allows management of the kernel NCSI state, such as the channel and
   package masks

 - allows issuing NCSI messages to the NCSI-capable NIC, through the
   NCSI_CMD_SEND_CMD interface

While these two things do share the same kernel API, they have somewhat
different objectives: one is controlling local state, the other is
controlling remote (ie, the NIC) state.

In future, we want to allow non-netlink-based NCSI transports for
issuing commands to the NIC, which makes the ncsi-netlink name somewhat
inaccurate for those.

So, introduce a new tool, 'ncsi-cmd', for issuing NCSI commands over the
netlink interface.

This has similar command-line semantics to the existing
'ncsi-netlink [...] -o <PAYLOAD>' usage, but has a few changes for a
more ergonomic UI:

Firstly, the type (or "opcode") byte is no longer packed into the
payload data, because it's not really payload.

Secondly, we use --interface/-i rather than --index/-x, with a note that
interfaces are specified by index. This allows for future changes that
allow specifying interfaces by name.

Finally, to make it clear that we can issue more than just OEM
commands, we have separate subcommands: "oem" and "raw". These are
similar, just that "oem" implies the standard OEM type value of 0x50.
So, the following are equivalent:

  ncsi-cmd -i2 -p0 oem 010203

  ncsi-cmd -i2 -p0 raw 0x50 010203

But now we have a cleaner interface for not-OEM commands:

  ncsi-cmd -i12 -p0 raw 0x15

For issuing command type 0x15, "Get Version ID".

We remove the send logic from ncsi-netlink, but leave a compatibility
shim that will exec() ncsi-cmd with the appropriate arguments instead.

Change-Id: Ied240db0d545d5770df0927da354c65b82ee9508
Signed-off-by: Jeremy Kerr <jk@codeconstruct.com.au>
diff --git a/src/ncsi_netlink_main.cpp b/src/ncsi_netlink_main.cpp
index 8859950..09010fb 100644
--- a/src/ncsi_netlink_main.cpp
+++ b/src/ncsi_netlink_main.cpp
@@ -16,6 +16,9 @@
 #include "argument.hpp"
 #include "ncsi_util.hpp"
 
+#include <string.h>
+#include <unistd.h>
+
 #include <phosphor-logging/lg2.hpp>
 #include <stdplus/numeric/str.hpp>
 #include <stdplus/str/buf.hpp>
@@ -74,26 +77,6 @@
     }
 }
 
-static stdplus::StrBuf toHexStr(std::span<const uint8_t> c) noexcept
-{
-    stdplus::StrBuf ret;
-    if (c.empty())
-    {
-        return ret;
-    }
-    stdplus::IntToStr<16, uint8_t> its;
-    auto oit = ret.append(c.size() * 3);
-    auto cit = c.begin();
-    oit = its(oit, *cit++, 2);
-    for (; cit != c.end(); ++cit)
-    {
-        *oit++ = ' ';
-        oit = its(oit, *cit, 2);
-    }
-    *oit = 0;
-    return ret;
-}
-
 int main(int argc, char** argv)
 {
     using namespace phosphor::network;
@@ -103,7 +86,6 @@
     int packageInt{};
     int channelInt{};
     int indexInt{};
-    int operationInt{DEFAULT_VALUE};
 
     // Parse out interface argument.
     auto ifIndex = (options)["index"];
@@ -159,69 +141,60 @@
     auto payloadStr = (options)["oem-payload"];
     if (!payloadStr.empty())
     {
-        std::string byte(2, '\0');
-        std::vector<unsigned char> payload;
-
-        if (payloadStr.size() % 2)
+        if (payloadStr.size() % 2 || payloadStr.size() < 2)
             exitWithError("Payload invalid: specify two hex digits per byte.",
                           argv);
 
-        // Parse the payload string (e.g. "50000001572100") to byte data
-        // The first two characters (i.e. "50") represent the Send Cmd Operation
-        // All remaining pairs, interpreted in hex radix, represent the command
-        // payload
-        int sendCmdSelect{};
-        for (unsigned int i = 1; i < payloadStr.size(); i += 2)
-        {
-            byte[0] = payloadStr[i - 1];
-            byte[1] = payloadStr[i];
-
-            try
-            {
-                sendCmdSelect = stoi(byte, nullptr, 16);
-            }
-            catch (const std::exception& e)
-            {
-                exitWithError("Payload invalid.", argv);
-            }
-            if (i == 1)
-            {
-                operationInt = sendCmdSelect;
-            }
-            else
-            {
-                payload.push_back(sendCmdSelect);
-            }
-        }
-
-        if (operationInt == DEFAULT_VALUE)
-        {
-            exitWithError("No payload specified.", argv);
-        }
+        // Payload string is in the format <type>[<payload>]
+        // (e.g. "50000001572100"), where the first two characters (i.e. "50")
+        // represent the command type, and the rest the payload. Split this
+        // up for the ncsi-cmd operation, which has these as separate arguments.
+        std::string typeStr(payloadStr.substr(0, 2));
+        std::string dataStr(payloadStr.substr(2));
 
         if (packageInt == DEFAULT_VALUE)
         {
             exitWithError("Package not specified.", argv);
         }
 
-        if (!payload.empty())
+        std::vector<std::string> args = {
+            "ncsi-cmd",
+            "-i",
+            std::to_string(indexInt),
+            "-p",
+            std::to_string(packageInt),
+            "raw",
+        };
+
+        if (channelInt != DEFAULT_VALUE)
         {
-            lg2::debug("Payload: {PAYLOAD}", "PAYLOAD", toHexStr(payload));
+            args.push_back("-c");
+            args.push_back(std::to_string(channelInt));
         }
 
-        std::optional<uint8_t> chan = channelInt != DEFAULT_VALUE
-                                          ? std::make_optional(channelInt)
-                                          : std::nullopt;
-        NCSICommand cmd(operationInt, packageInt, chan, payload);
+        args.push_back(typeStr);
+        args.push_back(dataStr);
 
-        auto resp = interface.sendCommand(cmd);
-        if (!resp)
+        /* Convert to C argv array. execvp()'s argv argument is not const,
+         * whereas .c_str() is, so we need to strdup here.
+         */
+        char** argv = new char*[args.size() + 1]();
+        for (size_t i = 0; i < args.size(); i++)
         {
-            return EXIT_FAILURE;
+            argv[i] = strdup(args[i].c_str());
         }
-        lg2::debug("Response {DATA_LEN} bytes: {DATA}", "DATA_LEN",
-                   resp->full_payload.size(), "DATA",
-                   toHexStr(resp->full_payload));
+        argv[args.size()] = NULL;
+
+        lg2::debug("ncsi-netlink [..] -o is deprecated by ncsi-cmd");
+        execvp(argv[0], argv);
+        lg2::error("exec failed; use ncsi-cmd directly");
+
+        for (size_t i = 0; i < args.size(); i++)
+        {
+            free(argv[i]);
+        }
+        delete[] argv;
+        return EXIT_FAILURE;
     }
     else if ((options)["set"] == "true")
     {