xref: /openbmc/phosphor-networkd/src/ncsi_cmd.cpp (revision 84f58c2d517721f46e362261a7010cee5a8bacac)
1 /**
2  * Copyright © 2018 IBM Corporation
3  * Copyright © 2024 Code Construct
4  *
5  * Licensed under the Apache License, Version 2.0 (the "License");
6  * you may not use this file except in compliance with the License.
7  * You may obtain a copy of the License at
8  *
9  *     http://www.apache.org/licenses/LICENSE-2.0
10  *
11  * Unless required by applicable law or agreed to in writing, software
12  * distributed under the License is distributed on an "AS IS" BASIS,
13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  * See the License for the specific language governing permissions and
15  * limitations under the License.
16  */
17 #include "ncsi_util.hpp"
18 
19 #include <assert.h>
20 #include <getopt.h>
21 #include <linux/mctp.h>
22 #include <stdint.h>
23 #include <stdlib.h>
24 #include <string.h>
25 
26 #include <phosphor-logging/lg2.hpp>
27 #include <stdplus/numeric/str.hpp>
28 #include <stdplus/str/buf.hpp>
29 #include <stdplus/str/conv.hpp>
30 
31 #include <climits>
32 #include <fstream>
33 #include <iostream>
34 #include <map>
35 #include <memory>
36 #include <optional>
37 #include <string_view>
38 #include <vector>
39 
40 using namespace phosphor::network::ncsi;
41 
42 const uint32_t NCSI_CORE_DUMP_HANDLE = 0xFFFF0000;
43 const uint32_t NCSI_CRASH_DUMP_HANDLE = 0xFFFF0001;
44 
45 struct GlobalOptions
46 {
47     std::unique_ptr<Interface> interface;
48     std::optional<uint8_t> package;
49     std::optional<uint8_t> channel;
50 };
51 
52 struct MCTPAddress
53 {
54     int network;
55     uint8_t eid;
56 };
57 
58 /* MCTP EIDs below 8 are invalid, 255 is broadcast */
59 static constexpr uint8_t MCTP_EID_MIN = 8;
60 static constexpr uint8_t MCTP_EID_MAX = 254;
61 
62 const struct option options[] = {
63     {"package", required_argument, NULL, 'p'},
64     {"channel", required_argument, NULL, 'c'},
65     {"interface", required_argument, NULL, 'i'},
66     {"mctp", required_argument, NULL, 'm'},
67     {"help", no_argument, NULL, 'h'},
68     {0, 0, 0, 0},
69 };
70 
71 static void print_usage(const char* progname)
72 {
73     // clang-format off
74     std::cerr
75         << "Usage:\n"
76         "  " << progname << " <options> raw TYPE [PAYLOAD...]\n"
77         "  " << progname << " <options> oem [PAYLOAD...]\n"
78         "  " << progname << " <options> discover\n"
79         "  " << progname << " <options> core-dump FILE\n"
80         "  " << progname << " <options> crash-dump FILE\n"
81         "\n"
82         "Global options:\n"
83         "    --interface IFACE, -i  Specify net device by ifindex.\n"
84         "    --mctp [NET,]EID, -m   Specify MCTP network device.\n"
85         "    --package PACKAGE, -p  For non-discovery commands this is required; for discovery it is optional and\n"
86         "                           restricts the discovery to a specific package index.\n"
87         "    --channel CHANNEL, -c  Specify a channel.\n"
88         "\n"
89         "A --package/-p argument is required, as well as interface type "
90         "(--interface/-i or --mctp/-m)\n"
91         "\n"
92         "Subcommands:\n"
93         "\n"
94         "discover\n"
95         "    Scan for available NC-SI packages and channels.\n"
96         "\n"
97         "raw TYPE [PAYLOAD...]\n"
98         "    Send a single command using raw type/payload data.\n"
99         "        TYPE               NC-SI command type, in hex\n"
100         "        PAYLOAD            Command payload bytes, as hex\n"
101         "\n"
102         "oem PAYLOAD\n"
103         "    Send a single OEM command (type 0x50).\n"
104         "        PAYLOAD            Command payload bytes, as hex\n"
105         "\n"
106         "core-dump FILE\n"
107         "    Perform NCSI core dump and save log to FILE.\n"
108         "\n"
109         "crash-dump FILE\n"
110         "    Perform NCSI crash dump and save log to FILE.\n";
111     // clang-format on
112 }
113 
114 static std::optional<unsigned int> parseUnsigned(const char* str,
115                                                  const char* label)
116 {
117     try
118     {
119         unsigned long tmp = std::stoul(str, NULL, 16);
120         if (tmp <= UINT_MAX)
121             return tmp;
122     }
123     catch (const std::exception& e)
124     {}
125     std::cerr << "Invalid " << label << " argument '" << str << "'\n";
126     return {};
127 }
128 
129 static std::optional<MCTPAddress> parseMCTPAddress(const std::string& str)
130 {
131     std::string::size_type sep = str.find(',');
132     std::string eid_str;
133     MCTPAddress addr;
134 
135     if (sep == std::string::npos)
136     {
137         addr.network = MCTP_NET_ANY;
138         eid_str = str;
139     }
140     else
141     {
142         std::string net_str = str.substr(0, sep);
143         try
144         {
145             addr.network = stoi(net_str);
146         }
147         catch (const std::exception& e)
148         {
149             return {};
150         }
151         eid_str = str.substr(sep + 1);
152     }
153 
154     unsigned long tmp;
155     try
156     {
157         tmp = stoul(eid_str);
158     }
159     catch (const std::exception& e)
160     {
161         return {};
162     }
163 
164     if (tmp < MCTP_EID_MIN || tmp > MCTP_EID_MAX)
165     {
166         return {};
167     }
168 
169     addr.eid = tmp;
170 
171     return addr;
172 }
173 
174 static std::optional<std::vector<unsigned char>> parsePayload(
175     int argc, const char* const argv[])
176 {
177     /* we have already checked that there are sufficient args in callers */
178     assert(argc >= 1);
179 
180     std::vector<unsigned char> payload;
181 
182     /* we support two formats of payload - all as one argument:
183      *   00010c202f
184      *
185      * or single bytes in separate arguments:
186      *   00 01 0c 20 2f
187      *
188      * both are assumed as entirely hex, but the latter format does not
189      * need to be exactly two chars per byte:
190      *   0 1 c 20 2f
191      */
192 
193     size_t len0 = strlen(argv[0]);
194     if (argc == 1 && len0 > 2)
195     {
196         /* single argument format, parse as multiple bytes */
197         if (len0 % 2 != 0)
198         {
199             std::cerr << "Invalid payload length " << len0
200                       << " (must be a multiple of 2 chars)\n";
201             return {};
202         }
203 
204         std::string str(argv[0]);
205         std::string_view sv(str);
206 
207         for (unsigned int i = 0; i < sv.size(); i += 2)
208         {
209             unsigned char byte;
210             auto begin = sv.data() + i;
211             auto end = begin + 2;
212 
213             auto [next, err] = std::from_chars(begin, end, byte, 16);
214 
215             if (err != std::errc() || next != end)
216             {
217                 std::cerr << "Invalid payload string\n";
218                 return {};
219             }
220             payload.push_back(byte);
221         }
222     }
223     else
224     {
225         /* multiple payload arguments, each is a separate hex byte */
226         for (int i = 0; i < argc; i++)
227         {
228             unsigned char byte;
229             auto begin = argv[i];
230             auto end = begin + strlen(begin);
231 
232             auto [next, err] = std::from_chars(begin, end, byte, 16);
233 
234             if (err != std::errc() || next != end)
235             {
236                 std::cerr << "Invalid payload argument '" << begin << "'\n";
237                 return {};
238             }
239             payload.push_back(byte);
240         }
241     }
242 
243     return payload;
244 }
245 
246 static std::optional<std::tuple<GlobalOptions, int>> parseGlobalOptions(
247     int argc, char* const* argv)
248 {
249     std::optional<unsigned int> chan, package, interface;
250     std::optional<MCTPAddress> mctp;
251     const char* progname = argv[0];
252     GlobalOptions opts{};
253 
254     for (;;)
255     {
256         /* We're using + here as we want to stop parsing at the subcommand
257          * name
258          */
259         int opt = getopt_long(argc, argv, "+p:c:i:m:h", options, NULL);
260         if (opt == -1)
261         {
262             break;
263         }
264 
265         switch (opt)
266         {
267             case 'i':
268                 interface = parseUnsigned(optarg, "interface");
269                 if (!interface.has_value())
270                 {
271                     return {};
272                 }
273                 break;
274 
275             case 'p':
276                 package = parseUnsigned(optarg, "package");
277                 if (!package.has_value())
278                 {
279                     return {};
280                 }
281                 break;
282 
283             case 'm':
284                 mctp = parseMCTPAddress(optarg);
285                 if (!mctp.has_value())
286                 {
287                     return {};
288                 }
289                 break;
290 
291             case 'c':
292                 chan = parseUnsigned(optarg, "channel");
293                 if (!chan.has_value())
294                 {
295                     return {};
296                 }
297                 opts.channel = *chan;
298                 break;
299 
300             case 'h':
301             default:
302                 print_usage(progname);
303                 return {};
304         }
305     }
306 
307     if (interface.has_value() && mctp.has_value())
308     {
309         std::cerr << "Only one of --interface or --mctp can be provided\n";
310         return {};
311     }
312     else if (interface.has_value())
313     {
314         opts.interface = std::make_unique<NetlinkInterface>(*interface);
315     }
316     else if (mctp.has_value())
317     {
318         MCTPAddress m = *mctp;
319         opts.interface = std::make_unique<MCTPInterface>(m.network, m.eid);
320     }
321     else
322     {
323         std::cerr << "Missing interface description, "
324                      "add a --mctp or --interface argument\n";
325         return {};
326     }
327 
328     // For non-discovery commands, package is required.
329     // If the subcommand is "discover", leave opts.package as nullopt.
330     if (!package.has_value())
331     {
332         if (optind < argc && std::string(argv[optind]) == "discover")
333         {
334             // Do nothing; package remains nullopt for discovery.
335         }
336         else
337         {
338             std::cerr << "Missing package, add a --package argument\n";
339             return {};
340         }
341     }
342     else
343     {
344         opts.package = static_cast<uint8_t>(*package);
345     }
346 
347     return std::make_tuple(std::move(opts), optind);
348 }
349 
350 static stdplus::StrBuf toHexStr(std::span<const uint8_t> c) noexcept
351 {
352     stdplus::StrBuf ret;
353     if (c.empty())
354     {
355         /* workaround for lg2's handling of string_view */
356         *ret.data() = '\0';
357         return ret;
358     }
359     stdplus::IntToStr<16, uint8_t> its;
360     auto oit = ret.append(c.size() * 3);
361     auto cit = c.begin();
362     oit = its(oit, *cit++, 2);
363     for (; cit != c.end(); ++cit)
364     {
365         *oit++ = ' ';
366         oit = its(oit, *cit, 2);
367     }
368     *oit = 0;
369     return ret;
370 }
371 
372 /* Helper for the 'raw' and 'oem' command handlers: Construct a single command,
373  * issue it to the interface, and print the resulting response payload.
374  */
375 static int ncsiCommand(GlobalOptions& options, uint8_t type,
376                        std::vector<unsigned char> payload)
377 {
378     uint8_t pkg = options.package.value_or(0);
379     auto ch = options.channel;
380 
381     NCSICommand cmd(type, pkg, ch,
382                     std::span<unsigned char>(payload.data(), payload.size()));
383     lg2::debug("Command: type {TYPE}, payload {PAYLOAD_LEN} bytes: {PAYLOAD}",
384                "TYPE", lg2::hex, type, "PAYLOAD_LEN", payload.size(), "PAYLOAD",
385                toHexStr(payload));
386 
387     auto resp = options.interface->sendCommand(cmd);
388     if (!resp)
389     {
390         return -1;
391     }
392 
393     lg2::debug("Response {DATA_LEN} bytes: {DATA}", "DATA_LEN",
394                resp->full_payload.size(), "DATA", toHexStr(resp->full_payload));
395 
396     return 0;
397 }
398 
399 static int ncsiCommandRaw(GlobalOptions& options, int argc,
400                           const char* const* argv)
401 {
402     std::vector<unsigned char> payload;
403     std::optional<uint8_t> type;
404 
405     if (argc < 2)
406     {
407         std::cerr << "Invalid arguments for 'raw' subcommand\n";
408         return -1;
409     }
410 
411     /* Not only does the type need to fit into one byte, but the top bit
412      * is used for the request/response flag, so check for 0x80 here as
413      * our max here.
414      */
415     type = parseUnsigned(argv[1], "command type");
416     if (!type.has_value() || *type > 0x80)
417     {
418         std::cerr << "Invalid command type value\n";
419         return -1;
420     }
421 
422     if (argc >= 3)
423     {
424         auto tmp = parsePayload(argc - 2, argv + 2);
425         if (!tmp.has_value())
426         {
427             return -1;
428         }
429 
430         payload = *tmp;
431     }
432 
433     return ncsiCommand(options, *type, payload);
434 }
435 
436 static int ncsiCommandOEM(GlobalOptions& options, int argc,
437                           const char* const* argv)
438 {
439     constexpr uint8_t oemType = 0x50;
440 
441     if (argc < 2)
442     {
443         std::cerr << "Invalid arguments for 'oem' subcommand\n";
444         return -1;
445     }
446 
447     auto payload = parsePayload(argc - 1, argv + 1);
448     if (!payload.has_value())
449     {
450         return -1;
451     }
452 
453     return ncsiCommand(options, oemType, *payload);
454 }
455 
456 static std::array<unsigned char, 12> generateDumpCmdPayload(
457     uint32_t chunkNum, uint32_t dataHandle, bool isAbort)
458 {
459     std::array<unsigned char, 12> payload = {};
460     uint8_t opcode;
461 
462     if (isAbort)
463     {
464         opcode = 3;
465     }
466     else if (chunkNum == 1)
467     {
468         // For the first chunk the chunk number field carries the data handle.
469         opcode = 0;
470         chunkNum = dataHandle;
471     }
472     else
473     {
474         opcode = 2;
475     }
476     payload[3] = opcode;
477     payload[8] = (chunkNum >> 24) & 0xFF;
478     payload[9] = (chunkNum >> 16) & 0xFF;
479     payload[10] = (chunkNum >> 8) & 0xFF;
480     payload[11] = chunkNum & 0xFF;
481 
482     return payload;
483 }
484 
485 std::string getDescForResponse(uint16_t response)
486 {
487     static const std::map<uint16_t, std::string> descMap = {
488         {0x0000, "Command Completed"},
489         {0x0001, "Command Failed"},
490         {0x0002, "Command Unavailable"},
491         {0x0003, "Command Unsupported"},
492         {0x0004, "Delayed Response"}};
493 
494     try
495     {
496         return descMap.at(response);
497     }
498     catch (std::exception&)
499     {
500         return "Unknown response code: " + std::to_string(response);
501     }
502 }
503 
504 std::string getDescForReason(uint16_t reason)
505 {
506     static const std::map<uint16_t, std::string> reasonMap = {
507         {0x0001, "Interface Initialization Required"},
508         {0x0002, "Parameter Is Invalid, Unsupported, or Out-of-Range"},
509         {0x0003, "Channel Not Ready"},
510         {0x0004, "Package Not Ready"},
511         {0x0005, "Invalid Payload Length"},
512         {0x0006, "Information Not Available"},
513         {0x0007, "Intervention Required"},
514         {0x0008, "Link Command Failed - Hardware Access Error"},
515         {0x0009, "Command Timeout"},
516         {0x000A, "Secondary Device Not Powered"},
517         {0x7FFF, "Unknown/Unsupported Command Type"},
518         {0x4D01, "Abort Transfer: NC cannot proceed with transfer."},
519         {0x4D02,
520          "Invalid Handle Value: Data Handle is invalid or not supported."},
521         {0x4D03,
522          "Sequence Count Error: Chunk Number requested is not consecutive with the previous number transmitted."}};
523 
524     if (reason >= 0x8000)
525     {
526         return "OEM Reason Code" + std::to_string(reason);
527     }
528 
529     try
530     {
531         return reasonMap.at(reason);
532     }
533     catch (std::exception&)
534     {
535         return "Unknown reason code: " + std::to_string(reason);
536     }
537 }
538 
539 static int ncsiDump(GlobalOptions& options, uint32_t handle,
540                     const std::string& fileName)
541 {
542     constexpr auto ncsiCmdDump = 0x4D;
543     uint32_t chunkNum = 1;
544     bool isTransferComplete = false;
545     bool isAbort = false;
546     uint8_t opcode = 0;
547     uint32_t totalDataSize = 0;
548     std::ofstream outFile(fileName, std::ios::binary);
549 
550     // Validate handle
551     if (handle != NCSI_CORE_DUMP_HANDLE && handle != NCSI_CRASH_DUMP_HANDLE)
552     {
553         std::cerr
554             << "Invalid data handle value. Expected NCSI_CORE_DUMP_HANDLE (0xFFFF0000) or NCSI_CRASH_DUMP_HANDLE (0xFFFF0001), got: "
555             << std::hex << handle << "\n";
556         if (outFile.is_open())
557             outFile.close();
558         return -1;
559     }
560 
561     if (!outFile.is_open())
562     {
563         std::cerr << "Failed to open file: " << fileName << "\n";
564         return -1;
565     }
566 
567     while (!isTransferComplete && !isAbort)
568     {
569         auto payloadArray = generateDumpCmdPayload(chunkNum, handle, false);
570         std::span<unsigned char> payload(payloadArray.data(),
571                                          payloadArray.size());
572         uint8_t pkg = options.package.value_or(0);
573         auto ch = options.channel;
574         NCSICommand cmd(ncsiCmdDump, pkg, ch, payload);
575         auto resp = options.interface->sendCommand(cmd);
576         if (!resp)
577         {
578             std::cerr << "Failed to send NCSI command for chunk number "
579                       << chunkNum << "\n";
580             outFile.close();
581             return -1;
582         }
583 
584         auto response = resp->response;
585         auto reason = resp->reason;
586         auto length = resp->payload.size();
587 
588         if (response != 0)
589         {
590             std::cerr << "Error encountered on chunk " << chunkNum << ":\n"
591                       << "Response Description: "
592                       << getDescForResponse(response) << "\n"
593                       << "Reason Description: " << getDescForReason(reason)
594                       << "\n";
595             outFile.close();
596             return -1;
597         }
598 
599         if (length > 8)
600         {
601             auto dataSize = length - 8;
602             totalDataSize += dataSize;
603             opcode = resp->payload[7];
604             if (outFile.is_open())
605             {
606                 outFile.write(
607                     reinterpret_cast<const char*>(resp->payload.data() + 8),
608                     dataSize);
609             }
610             else
611             {
612                 std::cerr << "Failed to write to file. File is not open.\n";
613                 isAbort = true;
614             }
615         }
616         else
617         {
618             std::cerr << "Received response with insufficient payload length: "
619                       << length << " Expected more than 8 bytes.  Chunk: "
620                       << chunkNum << "\n";
621             isAbort = true;
622         }
623 
624         switch (opcode)
625         {
626             case 0x1: // Initial chunk, continue to next
627             case 0x2: // Middle chunk, continue to next
628                 chunkNum++;
629                 break;
630             case 0x4: // Final chunk
631             case 0x5: // Initial and final chunk
632                 isTransferComplete = true;
633                 break;
634             case 0x8: // Abort transfer
635                 std::cerr << "Transfer aborted by NIC\n";
636                 isTransferComplete = true;
637                 break;
638             default:
639                 std::cerr << "Unexpected opcode: " << static_cast<int>(opcode)
640                           << " at chunk " << chunkNum << "\n";
641                 isAbort = true;
642                 break;
643         }
644     }
645 
646     // Handle abort explicitly if an unexpected opcode was encountered.
647     if (isAbort)
648     {
649         std::cerr << "Issuing explicit abort command...\n";
650         auto abortPayloadArray = generateDumpCmdPayload(chunkNum, handle, true);
651         std::span<unsigned char> abortPayload(abortPayloadArray.data(),
652                                               abortPayloadArray.size());
653         uint8_t pkg = options.package.value_or(0);
654         auto ch = options.channel;
655         NCSICommand abortCmd(ncsiCmdDump, pkg, ch, abortPayload);
656         auto abortResp = options.interface->sendCommand(abortCmd);
657         if (!abortResp)
658         {
659             std::cerr << "Failed to send abort command for chunk number "
660                       << chunkNum << "\n";
661         }
662         else
663         {
664             std::cerr << "Abort command issued.\n";
665         }
666     }
667     else
668     {
669         std::cout << "Dump transfer complete. Total data size: "
670                   << totalDataSize << " bytes\n";
671     }
672 
673     outFile.close();
674     return 0;
675 }
676 
677 static int ncsiCommandReceiveDump(GlobalOptions& options,
678                                   const std::string& subcommand, int argc,
679                                   const char* const* argv)
680 {
681     if (argc != 2)
682     {
683         std::cerr << "Invalid arguments for '" << subcommand
684                   << "' subcommand\n";
685         print_usage(argv[0]);
686         return -1;
687     }
688     uint32_t handle = (subcommand == "core-dump") ? NCSI_CORE_DUMP_HANDLE
689                                                   : NCSI_CRASH_DUMP_HANDLE;
690     return ncsiDump(options, handle, argv[1]);
691 }
692 
693 static int ncsiDiscover(GlobalOptions& options)
694 {
695     constexpr uint8_t ncsiClearInitialState = 0x00;
696     constexpr uint8_t ncsiGetCapabilities = 0x16;
697     constexpr unsigned int maxPackageIndex = 8;  // Packages 0–7
698     constexpr unsigned int maxChannelCount = 32; // Channels 0–31
699 
700     std::cout << "Starting NC-SI Package and Channel Discovery...\n";
701 
702     unsigned int startPackage = 0, endPackage = maxPackageIndex;
703     if (options.package.has_value())
704     {
705         startPackage = *options.package;
706         endPackage = *options.package;
707         std::cout << "Restricting discovery to Package " << *options.package
708                   << ".\n";
709     }
710 
711     for (unsigned int packageIndex = startPackage; packageIndex <= endPackage;
712          ++packageIndex)
713     {
714         std::cout << "Checking Package " << packageIndex << "...\n";
715 
716         // For each channel from 0..7, we:
717         //   1) Send Clear Initial State.
718         //   2) If that succeeds, send Get Capabilities.
719         //   3) If we get a valid response, we parse and stop.
720         bool foundChannel = false;
721         for (unsigned int channelIndex = 0; channelIndex < maxChannelCount;
722              ++channelIndex)
723         {
724             std::cout << "  Clearing Initial State on Channel " << channelIndex
725                       << "...\n";
726             {
727                 std::vector<unsigned char> clearPayload; // No payload
728                 NCSICommand clearCmd(ncsiClearInitialState,
729                                      static_cast<uint8_t>(packageIndex),
730                                      static_cast<uint8_t>(channelIndex),
731                                      std::span<unsigned char>(clearPayload));
732 
733                 auto clearResp = options.interface->sendCommand(clearCmd);
734                 if (!clearResp || clearResp->response != 0x0000)
735                 {
736                     std::cout << "    Clear Initial State failed on Channel "
737                               << channelIndex << ". Trying next channel.\n";
738                     continue; // Try next channel
739                 }
740             }
741 
742             // Now that Clear Initial State succeeded, try Get Capabilities
743             std::vector<unsigned char> payload; // No payload
744             NCSICommand getCapCmd(ncsiGetCapabilities,
745                                   static_cast<uint8_t>(packageIndex),
746                                   static_cast<uint8_t>(channelIndex),
747                                   std::span<unsigned char>(payload));
748 
749             auto resp = options.interface->sendCommand(getCapCmd);
750             if (resp && resp->response == 0x0000 &&
751                 resp->full_payload.size() >= 52)
752             {
753                 uint8_t channelCount = resp->full_payload[47];
754                 std::cout << "    Package " << packageIndex << " supports "
755                           << static_cast<int>(channelCount) << " channels.\n";
756                 std::cout << "    Found available Package " << packageIndex
757                           << ", Channel " << channelIndex
758                           << ". Stopping discovery.\n";
759                 foundChannel = true;
760                 break;
761             }
762             else
763             {
764                 std::cout << "    Channel " << channelIndex
765                           << " not responding. Trying next channel.\n";
766             }
767         } // end channel loop
768 
769         if (foundChannel)
770         {
771             // We found a channel on this package, so we stop discovery
772             return 0;
773         }
774         else
775         {
776             std::cout << "  No valid channels found for Package "
777                       << packageIndex << ". Moving to next package.\n";
778         }
779     } // end package loop
780 
781     std::cout
782         << "No available NC-SI packages or channels found. Discovery complete.\n";
783     return -1;
784 }
785 
786 /* A note on log output:
787  * For output that relates to command-line usage, we just output directly to
788  * stderr. Once we have a properly parsed command line invocation, we use lg2
789  * for log output, as we want that to use the standard log facilities to
790  * catch runtime error scenarios
791  */
792 int main(int argc, char** argv)
793 {
794     const char* progname = argv[0];
795 
796     auto opts = parseGlobalOptions(argc, argv);
797 
798     if (!opts.has_value())
799     {
800         return EXIT_FAILURE;
801     }
802 
803     auto [globalOptions, consumed] = std::move(*opts);
804 
805     if (consumed >= argc)
806     {
807         std::cerr << "Missing subcommand command type\n";
808         return EXIT_FAILURE;
809     }
810 
811     /* We have parsed the global options, advance argv & argc to allow the
812      * subcommand handlers to consume their own options
813      */
814     argc -= consumed;
815     argv += consumed;
816 
817     std::string subcommand = argv[0];
818 
819     // For non-discovery commands, package must be provided.
820     if (subcommand != "discover" && !globalOptions.package.has_value())
821     {
822         std::cerr << "Missing package, add a --package argument\n";
823         return EXIT_FAILURE;
824     }
825 
826     int ret = -1;
827 
828     if (subcommand == "raw")
829     {
830         ret = ncsiCommandRaw(globalOptions, argc, argv);
831     }
832     else if (subcommand == "oem")
833     {
834         ret = ncsiCommandOEM(globalOptions, argc, argv);
835     }
836     else if (subcommand == "core-dump" || subcommand == "crash-dump")
837     {
838         ret = ncsiCommandReceiveDump(globalOptions, subcommand, argc, argv);
839     }
840     else if (subcommand == "discover")
841     {
842         return ncsiDiscover(globalOptions);
843     }
844     else
845     {
846         std::cerr << "Unknown subcommand '" << subcommand << "'\n";
847         print_usage(progname);
848     }
849 
850     return ret ? EXIT_FAILURE : EXIT_SUCCESS;
851 }
852