1 /* 2 * Copyright (c) 2008, 2010, Oracle and/or its affiliates. All rights reserved. 3 * Copyright 2015 Nexenta Systems, Inc. All rights reserved. 4 * Copyright (c) 2018, Joyent, Inc. 5 */ 6 7 /* 8 * BSD 3 Clause License 9 * 10 * Copyright (c) 2007, The Storage Networking Industry Association. 11 * 12 * Redistribution and use in source and binary forms, with or without 13 * modification, are permitted provided that the following conditions 14 * are met: 15 * - Redistributions of source code must retain the above copyright 16 * notice, this list of conditions and the following disclaimer. 17 * 18 * - Redistributions in binary form must reproduce the above copyright 19 * notice, this list of conditions and the following disclaimer in 20 * the documentation and/or other materials provided with the 21 * distribution. 22 * 23 * - Neither the name of The Storage Networking Industry Association (SNIA) 24 * nor the names of its contributors may be used to endorse or promote 25 * products derived from this software without specific prior written 26 * permission. 27 * 28 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 29 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 30 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 31 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 32 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 33 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 34 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 35 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 36 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 37 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 38 * POSSIBILITY OF SUCH DAMAGE. 39 */ 40 #include <assert.h> 41 #include <ctype.h> 42 #include <libgen.h> 43 #include <libintl.h> 44 #include <locale.h> 45 #include <stddef.h> 46 #include <stdio.h> 47 #include <stdlib.h> 48 #include <strings.h> 49 #include <unistd.h> 50 #include <fcntl.h> 51 #include <sys/stat.h> 52 #include <door.h> 53 #include <sys/mman.h> 54 #include <libndmp.h> 55 #include "ndmpadm.h" 56 57 typedef enum { 58 HELP_GET_CONFIG, 59 HELP_SET_CONFIG, 60 HELP_SHOW_DEVICES, 61 HELP_SHOW_SESSIONS, 62 HELP_KILL_SESSIONS, 63 HELP_ENABLE_AUTH, 64 HELP_DISABLE_AUTH 65 } ndmp_help_t; 66 67 typedef struct ndmp_command { 68 const char *nc_name; 69 int (*func)(int argc, char **argv, 70 struct ndmp_command *cur_cmd); 71 ndmp_help_t nc_usage; 72 } ndmp_command_t; 73 74 static int ndmp_get_config(int, char **, ndmp_command_t *); 75 static int ndmp_set_config(int, char **, ndmp_command_t *); 76 static int ndmp_show_devices(int, char **, ndmp_command_t *); 77 static int ndmp_show_sessions(int, char **, ndmp_command_t *); 78 static int ndmp_kill_sessions(int, char **, ndmp_command_t *); 79 static int ndmp_enable_auth(int, char **, ndmp_command_t *); 80 static int ndmp_disable_auth(int, char **, ndmp_command_t *); 81 static void ndmp_get_config_process(char *); 82 static void ndmp_set_config_process(char *arg); 83 static int ndmp_get_password(char **); 84 85 static ndmp_command_t command_table[] = { 86 { "get", ndmp_get_config, HELP_GET_CONFIG }, 87 { "set", ndmp_set_config, HELP_SET_CONFIG }, 88 { "show-devices", ndmp_show_devices, HELP_SHOW_DEVICES }, 89 { "show-sessions", ndmp_show_sessions, HELP_SHOW_SESSIONS }, 90 { "kill-sessions", ndmp_kill_sessions, HELP_KILL_SESSIONS }, 91 { "enable", ndmp_enable_auth, HELP_ENABLE_AUTH }, 92 { "disable", ndmp_disable_auth, HELP_DISABLE_AUTH } 93 }; 94 95 #define NCOMMAND (sizeof (command_table) / sizeof (command_table[0])) 96 97 static char *prop_table[] = { 98 "debug-path", 99 "dump-pathnode", 100 "tar-pathnode", 101 "ignore-ctime", 102 "token-maxseq", 103 "version", 104 "dar-support", 105 "tcp-port", 106 "backup-quarantine", 107 "restore-quarantine", 108 "overwrite-quarantine", 109 "zfs-force-override", 110 "drive-type", 111 "debug-mode" 112 }; 113 114 #define NDMPADM_NPROP (sizeof (prop_table) / sizeof (prop_table[0])) 115 116 typedef struct ndmp_auth { 117 const char *auth_type; 118 const char *username; 119 const char *password; 120 } ndmp_auth_t; 121 122 static ndmp_auth_t ndmp_auth_table[] = { 123 { "cram-md5", "cram-md5-username", "cram-md5-password" }, 124 { "cleartext", "cleartext-username", "cleartext-password" } 125 }; 126 #define NAUTH (sizeof (ndmp_auth_table) / sizeof (ndmp_auth_table[0])) 127 #define NDMP_PASSWORD_RETRIES 3 128 129 #if !defined(TEXT_DOMAIN) 130 #define TEXT_DOMAIN "SYS_TEST" 131 #endif 132 133 static const char * 134 get_usage(ndmp_help_t idx) 135 { 136 switch (idx) { 137 case HELP_SET_CONFIG: 138 return ("\tset [-p] <property=value> [[-p] property=value] " 139 "...\n"); 140 case HELP_GET_CONFIG: 141 return ("\tget [-p] [property] [[-p] property] ...\n"); 142 case HELP_SHOW_DEVICES: 143 return ("\tshow-devices\n"); 144 case HELP_SHOW_SESSIONS: 145 return ("\tshow-sessions [-i tape,scsi,data,mover] [id] ...\n"); 146 case HELP_KILL_SESSIONS: 147 return ("\tkill-sessions <id ...>\n"); 148 case HELP_ENABLE_AUTH: 149 return ("\tenable <-a auth-type> <-u username>\n"); 150 case HELP_DISABLE_AUTH: 151 return ("\tdisable <-a auth-type>\n"); 152 } 153 154 return (NULL); 155 } 156 157 /* 158 * Display usage message. If we're inside a command, display only the usage for 159 * that command. Otherwise, iterate over the entire command table and display 160 * a complete usage message. 161 */ 162 static void 163 usage(boolean_t requested, ndmp_command_t *current_command) 164 { 165 int i; 166 boolean_t show_properties = B_FALSE; 167 FILE *fp = requested ? stdout : stderr; 168 169 if (current_command == NULL) { 170 (void) fprintf(fp, 171 gettext("Usage: ndmpadm subcommand args ...\n")); 172 (void) fprintf(fp, 173 gettext("where 'command' is one of the following:\n\n")); 174 175 for (i = 0; i < NCOMMAND; i++) { 176 (void) fprintf(fp, "%s", 177 get_usage(command_table[i].nc_usage)); 178 } 179 (void) fprintf(fp, gettext("\t\twhere %s can be either " 180 "%s or %s\n"), "'auth-type'", "'cram-md5'", "'cleartext'"); 181 } else { 182 (void) fprintf(fp, gettext("Usage:\n")); 183 (void) fprintf(fp, "%s", get_usage(current_command->nc_usage)); 184 if ((current_command->nc_usage == HELP_ENABLE_AUTH) || 185 (current_command->nc_usage == HELP_DISABLE_AUTH)) 186 (void) fprintf(fp, gettext("\t\twhere %s can be either " 187 "%s or %s\n"), 188 "'auth-type'", "'cram-md5'", "'cleartext'"); 189 } 190 191 if (current_command != NULL && 192 (strcmp(current_command->nc_name, "set") == 0)) 193 show_properties = B_TRUE; 194 195 if (show_properties) { 196 (void) fprintf(fp, 197 gettext("\nThe following properties are supported:\n")); 198 199 (void) fprintf(fp, gettext("\n\tPROPERTY")); 200 (void) fprintf(fp, "\n\t%s", "-------------"); 201 for (i = 0; i < NDMPADM_NPROP; i++) 202 (void) fprintf(fp, "\n\t%s", prop_table[i]); 203 (void) fprintf(fp, "\n"); 204 } 205 206 exit(requested ? 0 : 2); 207 } 208 209 /*ARGSUSED*/ 210 static int 211 ndmp_get_config(int argc, char **argv, ndmp_command_t *cur_cmd) 212 { 213 char *propval; 214 int i, c; 215 216 if (argc == 1) { 217 /* 218 * Get all the properties and variables ndmpadm is allowed 219 * to see. 220 */ 221 for (i = 0; i < NDMPADM_NPROP; i++) { 222 if (ndmp_get_prop(prop_table[i], &propval)) { 223 (void) fprintf(stdout, "\t%s=\n", 224 prop_table[i]); 225 } else { 226 (void) fprintf(stdout, "\t%s=%s\n", 227 prop_table[i], propval); 228 free(propval); 229 } 230 } 231 } else if (argc > 1) { 232 while ((c = getopt(argc, argv, ":p:")) != -1) { 233 switch (c) { 234 case 'p': 235 ndmp_get_config_process(optarg); 236 break; 237 case ':': 238 (void) fprintf(stderr, gettext("Option -%c " 239 "requires an operand\n"), optopt); 240 break; 241 case '?': 242 (void) fprintf(stderr, gettext("Unrecognized " 243 "option: -%c\n"), optopt); 244 } 245 } 246 /* 247 * optind is initialized to 1 if the -p option is not used, 248 * otherwise index to argv. 249 */ 250 argc -= optind; 251 argv += optind; 252 253 for (i = 0; i < argc; i++) { 254 if (strncmp(argv[i], "-p", 2) == 0) 255 continue; 256 257 ndmp_get_config_process(argv[i]); 258 } 259 } 260 return (0); 261 } 262 263 static void 264 ndmp_get_config_process(char *arg) 265 { 266 int j; 267 char *propval; 268 269 for (j = 0; j < NDMPADM_NPROP; j++) { 270 if (strcmp(arg, prop_table[j]) == 0) { 271 if (ndmp_get_prop(arg, &propval)) { 272 (void) fprintf(stdout, "\t%s=\n", arg); 273 } else { 274 (void) fprintf(stdout, "\t%s=%s\n", 275 arg, propval); 276 free(propval); 277 } 278 break; 279 } 280 } 281 if (j == NDMPADM_NPROP) { 282 (void) fprintf(stdout, gettext("\t%s is invalid property " 283 "or variable\n"), arg); 284 } 285 } 286 287 /*ARGSUSED*/ 288 static int 289 ndmp_set_config(int argc, char **argv, ndmp_command_t *cur_cmd) 290 { 291 int c, i; 292 293 if (argc < 2) { 294 (void) fprintf(stderr, gettext("Missing property=value " 295 "argument\n")); 296 usage(B_FALSE, cur_cmd); 297 } 298 while ((c = getopt(argc, argv, ":p:")) != -1) { 299 switch (c) { 300 case 'p': 301 ndmp_set_config_process(optarg); 302 break; 303 case ':': 304 (void) fprintf(stderr, gettext("Option -%c " 305 "requires an operand\n"), optopt); 306 break; 307 case '?': 308 (void) fprintf(stderr, gettext("Unrecognized " 309 "option: -%c\n"), optopt); 310 } 311 } 312 /* 313 * optind is initialized to 1 if the -p option is not used, 314 * otherwise index to argv. 315 */ 316 argc -= optind; 317 argv += optind; 318 319 for (i = 0; i < argc; i++) { 320 if (strncmp(argv[i], "-p", 2) == 0) 321 continue; 322 323 ndmp_set_config_process(argv[i]); 324 } 325 return (0); 326 } 327 328 static void 329 ndmp_set_config_process(char *propname) 330 { 331 char *propvalue; 332 int ret, j; 333 334 if ((propvalue = strchr(propname, '=')) == NULL) { 335 (void) fprintf(stderr, gettext("Missing value in " 336 "property=value argument for %s\n"), propname); 337 return; 338 } 339 *propvalue = '\0'; 340 propvalue++; 341 342 if (*propname == '\0') { 343 (void) fprintf(stderr, gettext("Missing property in " 344 "property=value argument for %s\n"), propname); 345 return; 346 } 347 for (j = 0; j < NDMPADM_NPROP; j++) { 348 if (strcmp(propname, prop_table[j]) == 0) 349 break; 350 } 351 if (j == NDMPADM_NPROP) { 352 (void) fprintf(stdout, gettext("%s is invalid property or " 353 "variable\n"), propname); 354 return; 355 } 356 ret = ndmp_set_prop(propname, propvalue); 357 if (ret != -1) { 358 if (!ndmp_door_status()) { 359 if (ndmp_service_refresh() != 0) 360 (void) fprintf(stdout, gettext("Could not " 361 "refesh property of service ndmpd\n")); 362 } 363 } else { 364 (void) fprintf(stdout, gettext("Could not set property for " 365 "%s - %s\n"), propname, ndmp_strerror(ndmp_errno)); 366 } 367 } 368 369 /*ARGSUSED*/ 370 static int 371 ndmp_show_devices(int argc, char **argv, ndmp_command_t *cur_cmd) 372 { 373 int ret; 374 ndmp_devinfo_t *dip = NULL; 375 size_t size; 376 377 if (ndmp_door_status()) { 378 (void) fprintf(stdout, 379 gettext("Service ndmpd not running\n")); 380 return (-1); 381 } 382 383 ret = ndmp_get_devinfo(&dip, &size); 384 385 if (ret == -1) 386 (void) fprintf(stdout, 387 gettext("Could not get device information\n")); 388 else 389 ndmp_devinfo_print(dip, size); 390 391 ndmp_get_devinfo_free(dip, size); 392 return (0); 393 } 394 395 static int 396 ndmp_show_sessions(int argc, char **argv, ndmp_command_t *cur_cmd) 397 { 398 ndmp_session_info_t *sinfo = NULL; 399 ndmp_session_info_t *sp = NULL; 400 uint_t num; 401 int c, ret, i, j; 402 int statarg = 0; 403 char *value; 404 char *type_subopts[] = { "tape", "scsi", "data", "mover", NULL }; 405 406 if (ndmp_door_status()) { 407 (void) fprintf(stdout, 408 gettext("Service ndmpd not running\n")); 409 return (-1); 410 } 411 412 /* Detail output if no option is specified */ 413 if (argc == 1) { 414 statarg = NDMP_CAT_ALL; 415 } else { 416 statarg = 0; 417 while ((c = getopt(argc, argv, ":i:")) != -1) { 418 switch (c) { 419 case 'i': 420 while (*optarg != '\0') { 421 switch (getsubopt(&optarg, type_subopts, 422 &value)) { 423 case 0: 424 statarg |= NDMP_CAT_TAPE; 425 break; 426 case 1: 427 statarg |= NDMP_CAT_SCSI; 428 break; 429 case 2: 430 statarg |= NDMP_CAT_DATA; 431 break; 432 case 3: 433 statarg |= NDMP_CAT_MOVER; 434 break; 435 default: 436 (void) fprintf(stderr, 437 gettext("Invalid object " 438 "type '%s'\n"), value); 439 usage(B_FALSE, cur_cmd); 440 } 441 } 442 break; 443 case ':': 444 (void) fprintf(stderr, 445 gettext("Missing argument for " 446 "'%c' option\n"), optopt); 447 usage(B_FALSE, cur_cmd); 448 break; 449 case '?': 450 (void) fprintf(stderr, 451 gettext("Invalid option '%c'\n"), optopt); 452 usage(B_FALSE, cur_cmd); 453 } 454 } 455 /* if -i and its argument are not specified, display all */ 456 if (statarg == 0) 457 statarg = NDMP_CAT_ALL; 458 } 459 /* 460 * optind is initialized to 1 if the -i option is not used, otherwise 461 * index to argv. 462 */ 463 argc -= optind; 464 argv += optind; 465 466 ret = ndmp_get_session_info(&sinfo, &num); 467 if (ret == -1) { 468 (void) fprintf(stdout, 469 gettext("Could not get session information\n")); 470 } else { 471 if (argc == 0) { 472 ndmp_session_all_print(statarg, sinfo, num); 473 } else { 474 for (i = 0; i < argc; i++) { 475 sp = sinfo; 476 for (j = 0; j < num; j++, sp++) { 477 if (sp->nsi_sid == atoi(argv[i])) { 478 ndmp_session_print(statarg, sp); 479 (void) fprintf(stdout, "\n"); 480 break; 481 } 482 } 483 if (j == num) { 484 (void) fprintf(stdout, 485 gettext("Session %d not " 486 "found\n"), atoi(argv[i])); 487 } 488 } 489 } 490 ndmp_get_session_info_free(sinfo, num); 491 } 492 return (0); 493 } 494 495 /*ARGSUSED*/ 496 static int 497 ndmp_kill_sessions(int argc, char **argv, ndmp_command_t *cur_cmd) 498 { 499 int ret, i; 500 501 if (ndmp_door_status()) { 502 (void) fprintf(stdout, 503 gettext("Service ndmpd not running.\n")); 504 return (-1); 505 } 506 507 /* If no arg is specified, print the usage and exit */ 508 if (argc == 1) 509 usage(B_FALSE, cur_cmd); 510 511 for (i = 1; i < argc; i++) { 512 if (atoi(argv[i]) > 0) { 513 ret = ndmp_terminate_session(atoi(argv[i])); 514 } else { 515 (void) fprintf(stderr, 516 gettext("Invalid argument %s\n"), argv[i]); 517 continue; 518 } 519 if (ret == -1) 520 (void) fprintf(stdout, 521 gettext("Session id %d not found.\n"), 522 atoi(argv[i])); 523 } 524 return (0); 525 } 526 527 static int 528 ndmp_get_password(char **password) 529 { 530 char *pw1, pw2[257]; 531 int i; 532 533 for (i = 0; i < NDMP_PASSWORD_RETRIES; i++) { 534 /* 535 * getpassphrase use the same buffer to return password, so 536 * copy the result in different buffer, before calling the 537 * getpassphrase again. 538 */ 539 if ((pw1 = 540 getpassphrase(gettext("Enter new password: "))) != NULL) { 541 (void) strlcpy(pw2, pw1, sizeof (pw2)); 542 if ((pw1 = 543 getpassphrase(gettext("Re-enter password: "))) 544 != NULL) { 545 if (strncmp(pw1, pw2, strlen(pw1)) == 0) { 546 *password = pw1; 547 return (0); 548 } else { 549 (void) fprintf(stderr, 550 gettext("Both password did not " 551 "match.\n")); 552 } 553 } 554 } 555 } 556 return (-1); 557 } 558 559 static int 560 ndmp_enable_auth(int argc, char **argv, ndmp_command_t *cur_cmd) 561 { 562 char *auth_type, *username, *password; 563 int c, i, auth_type_flag = 0; 564 char *enc_password; 565 566 /* enable <-a auth-type> <-u username> */ 567 if (argc != 5) { 568 usage(B_FALSE, cur_cmd); 569 } 570 571 while ((c = getopt(argc, argv, ":a:u:")) != -1) { 572 switch (c) { 573 case 'a': 574 auth_type = strdup(optarg); 575 break; 576 case 'u': 577 username = strdup(optarg); 578 break; 579 case ':': 580 (void) fprintf(stderr, gettext("Option -%c " 581 "requires an operand\n"), optopt); 582 usage(B_FALSE, cur_cmd); 583 break; 584 case '?': 585 (void) fprintf(stderr, gettext("Unrecognized " 586 "option: -%c\n"), optopt); 587 usage(B_FALSE, cur_cmd); 588 } 589 } 590 591 if ((auth_type) && (username)) { 592 if (ndmp_get_password(&password)) { 593 (void) fprintf(stderr, gettext("Could not get correct " 594 "password, exiting...")); 595 free(auth_type); 596 free(username); 597 exit(-1); 598 } 599 } else { 600 (void) fprintf(stderr, gettext("%s or %s can not be blank"), 601 "'auth-type'", "'username'"); 602 free(auth_type); 603 free(username); 604 exit(-1); 605 } 606 607 if ((enc_password = ndmp_base64_encode(password)) == NULL) { 608 (void) fprintf(stdout, 609 gettext("Could not encode password - %s\n"), 610 ndmp_strerror(ndmp_errno)); 611 free(auth_type); 612 free(username); 613 exit(-1); 614 } 615 616 for (i = 0; i < NAUTH; i++) { 617 if (strncmp(auth_type, ndmp_auth_table[i].auth_type, 618 strlen(ndmp_auth_table[i].auth_type)) == 0) { 619 auth_type_flag = 1; 620 if ((ndmp_set_prop(ndmp_auth_table[i].username, 621 username)) == -1) { 622 (void) fprintf(stdout, 623 gettext("Could not set username - %s\n"), 624 ndmp_strerror(ndmp_errno)); 625 continue; 626 } 627 if ((ndmp_set_prop(ndmp_auth_table[i].password, 628 enc_password)) == -1) { 629 (void) fprintf(stdout, 630 gettext("Could not set password - %s\n"), 631 ndmp_strerror(ndmp_errno)); 632 continue; 633 } 634 if (!ndmp_door_status() && 635 (ndmp_service_refresh()) != 0) { 636 (void) fprintf(stdout, 637 gettext("Could not refesh ndmpd service " 638 "properties\n")); 639 } 640 } 641 } 642 free(auth_type); 643 free(username); 644 free(enc_password); 645 646 if (!auth_type_flag) 647 usage(B_FALSE, cur_cmd); 648 649 return (0); 650 } 651 652 static int 653 ndmp_disable_auth(int argc, char **argv, ndmp_command_t *cur_cmd) 654 { 655 char *auth_type; 656 int c, i, auth_type_flag = 0; 657 658 /* disable <-a auth-type> */ 659 if (argc != 3) { 660 usage(B_FALSE, cur_cmd); 661 } 662 663 while ((c = getopt(argc, argv, ":a:")) != -1) { 664 switch (c) { 665 case 'a': 666 auth_type = strdup(optarg); 667 break; 668 case ':': 669 (void) fprintf(stderr, gettext("Option -%c " 670 "requires an operand\n"), optopt); 671 break; 672 case '?': 673 (void) fprintf(stderr, gettext("Unrecognized " 674 "option: -%c\n"), optopt); 675 } 676 } 677 for (i = 0; i < NAUTH; i++) { 678 if (strncmp(auth_type, ndmp_auth_table[i].auth_type, 679 strlen(ndmp_auth_table[i].auth_type)) == 0) { 680 auth_type_flag = 1; 681 if ((ndmp_set_prop(ndmp_auth_table[i].username, 682 "")) == -1) { 683 (void) fprintf(stdout, 684 gettext("Could not clear username - %s\n"), 685 ndmp_strerror(ndmp_errno)); 686 continue; 687 } 688 if ((ndmp_set_prop(ndmp_auth_table[i].password, 689 "")) == -1) { 690 (void) fprintf(stdout, 691 gettext("Could not clear password - %s\n"), 692 ndmp_strerror(ndmp_errno)); 693 continue; 694 } 695 if (!ndmp_door_status() && 696 (ndmp_service_refresh()) != 0) { 697 (void) fprintf(stdout, gettext("Could not " 698 "refesh ndmpd service properties\n")); 699 } 700 } 701 } 702 free(auth_type); 703 704 if (!auth_type_flag) 705 usage(B_FALSE, cur_cmd); 706 707 return (0); 708 } 709 710 int 711 main(int argc, char **argv) 712 { 713 int ret; 714 int i; 715 char *cmdname; 716 ndmp_command_t *current_command = NULL; 717 718 (void) setlocale(LC_ALL, ""); 719 (void) textdomain(TEXT_DOMAIN); 720 721 opterr = 0; 722 723 /* Make sure the user has specified some command. */ 724 if (argc < 2) { 725 (void) fprintf(stderr, gettext("Missing command.\n")); 726 usage(B_FALSE, current_command); 727 } 728 729 cmdname = argv[1]; 730 731 /* 732 * Special case '-?' 733 */ 734 if (strcmp(cmdname, "-?") == 0) 735 usage(B_TRUE, current_command); 736 737 /* 738 * Run the appropriate sub-command. 739 */ 740 for (i = 0; i < NCOMMAND; i++) { 741 if (strcmp(cmdname, command_table[i].nc_name) == 0) { 742 current_command = &command_table[i]; 743 ret = command_table[i].func(argc - 1, argv + 1, 744 current_command); 745 break; 746 } 747 } 748 749 if (i == NCOMMAND) { 750 (void) fprintf(stderr, gettext("Unrecognized " 751 "command '%s'\n"), cmdname); 752 usage(B_FALSE, current_command); 753 } 754 755 return (ret); 756 } 757