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