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