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