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