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