1 /* 2 * Copyright 2008 Sun Microsystems, Inc. All rights reserved. 3 * Use is subject to license terms. 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 }; 109 110 #define NDMPADM_NPROP (sizeof (prop_table) / sizeof (prop_table[0])) 111 112 typedef struct ndmp_auth { 113 const char *auth_type; 114 const char *username; 115 const char *password; 116 } ndmp_auth_t; 117 118 static ndmp_auth_t ndmp_auth_table[] = { 119 { "cram-md5", "cram-md5-username", "cram-md5-password" }, 120 { "cleartext", "cleartext-username", "cleartext-password" } 121 }; 122 #define NAUTH (sizeof (ndmp_auth_table) / sizeof (ndmp_auth_table[0])) 123 #define NDMP_PASSWORD_RETRIES 3 124 125 #if !defined(TEXT_DOMAIN) 126 #define TEXT_DOMAIN "SYS_TEST" 127 #endif 128 129 static const char * 130 get_usage(ndmp_help_t idx) 131 { 132 switch (idx) { 133 case HELP_SET_CONFIG: 134 return ("\tset [-p] <property=value> [[-p] property=value] " 135 "...\n"); 136 case HELP_GET_CONFIG: 137 return ("\tget [-p] [property] [[-p] property] ...\n"); 138 case HELP_SHOW_DEVICES: 139 return ("\tshow-devices\n"); 140 case HELP_SHOW_SESSIONS: 141 return ("\tshow-sessions [-i tape,scsi,data,mover] [id] ...\n"); 142 case HELP_KILL_SESSIONS: 143 return ("\tkill-sessions <id ...>\n"); 144 case HELP_ENABLE_AUTH: 145 return ("\tenable <-a auth-type> <-u username>\n"); 146 case HELP_DISABLE_AUTH: 147 return ("\tdisable <-a auth-type>\n"); 148 } 149 150 return (NULL); 151 } 152 153 /* 154 * Display usage message. If we're inside a command, display only the usage for 155 * that command. Otherwise, iterate over the entire command table and display 156 * a complete usage message. 157 */ 158 static void 159 usage(boolean_t requested, ndmp_command_t *current_command) 160 { 161 int i; 162 boolean_t show_properties = B_FALSE; 163 FILE *fp = requested ? stdout : stderr; 164 165 if (current_command == NULL) { 166 (void) fprintf(fp, 167 gettext("Usage: ndmpadm subcommand args ...\n")); 168 (void) fprintf(fp, 169 gettext("where 'command' is one of the following:\n\n")); 170 171 for (i = 0; i < NCOMMAND; i++) { 172 (void) fprintf(fp, "%s", 173 get_usage(command_table[i].nc_usage)); 174 } 175 (void) fprintf(fp, gettext("\t\twhere %s can be either " 176 "%s or %s\n"), "'auth-type'", "'cram-md5'", "'cleartext'"); 177 } else { 178 (void) fprintf(fp, gettext("Usage:\n")); 179 (void) fprintf(fp, "%s", get_usage(current_command->nc_usage)); 180 if ((current_command->nc_usage == HELP_ENABLE_AUTH) || 181 (current_command->nc_usage == HELP_DISABLE_AUTH)) 182 (void) fprintf(fp, gettext("\t\twhere %s can be either " 183 "%s or %s\n"), 184 "'auth-type'", "'cram-md5'", "'cleartext'"); 185 } 186 187 if (current_command != NULL && 188 (strcmp(current_command->nc_name, "set") == 0)) 189 show_properties = B_TRUE; 190 191 if (show_properties) { 192 (void) fprintf(fp, 193 gettext("\nThe following properties are supported:\n")); 194 195 (void) fprintf(fp, gettext("\n\tPROPERTY")); 196 (void) fprintf(fp, "\n\t%s", "-------------"); 197 for (i = 0; i < NDMPADM_NPROP; i++) 198 (void) fprintf(fp, "\n\t%s", prop_table[i]); 199 (void) fprintf(fp, "\n"); 200 } 201 202 exit(requested ? 0 : 2); 203 } 204 205 /*ARGSUSED*/ 206 static int 207 ndmp_get_config(int argc, char **argv, ndmp_command_t *cur_cmd) 208 { 209 char *propval; 210 int i, c; 211 212 if (argc == 1) { 213 /* 214 * Get all the properties and variables ndmpadm is allowed 215 * to see. 216 */ 217 for (i = 0; i < NDMPADM_NPROP; i++) { 218 if (ndmp_get_prop(prop_table[i], &propval)) { 219 (void) fprintf(stdout, "\t%s=\n", 220 prop_table[i]); 221 } else { 222 (void) fprintf(stdout, "\t%s=%s\n", 223 prop_table[i], propval); 224 free(propval); 225 } 226 } 227 } else if (argc > 1) { 228 while ((c = getopt(argc, argv, ":p:")) != -1) { 229 switch (c) { 230 case 'p': 231 ndmp_get_config_process(optarg); 232 break; 233 case ':': 234 (void) fprintf(stderr, gettext("Option -%c " 235 "requires an operand\n"), optopt); 236 break; 237 case '?': 238 (void) fprintf(stderr, gettext("Unrecognized " 239 "option: -%c\n"), optopt); 240 } 241 } 242 /* 243 * optind is initialized to 1 if the -p option is not used, 244 * otherwise index to argv. 245 */ 246 argc -= optind; 247 argv += optind; 248 249 for (i = 0; i < argc; i++) { 250 if (strncmp(argv[i], "-p", 2) == 0) 251 continue; 252 253 ndmp_get_config_process(argv[i]); 254 } 255 } 256 return (0); 257 } 258 259 static void 260 ndmp_get_config_process(char *arg) 261 { 262 int j; 263 char *propval; 264 265 for (j = 0; j < NDMPADM_NPROP; j++) { 266 if (strcmp(arg, prop_table[j]) == 0) { 267 if (ndmp_get_prop(arg, &propval)) { 268 (void) fprintf(stdout, "\t%s=\n", arg); 269 } else { 270 (void) fprintf(stdout, "\t%s=%s\n", 271 arg, propval); 272 free(propval); 273 } 274 break; 275 } 276 } 277 if (j == NDMPADM_NPROP) { 278 (void) fprintf(stdout, gettext("\t%s is invalid property " 279 "or variable\n"), arg); 280 } 281 } 282 283 /*ARGSUSED*/ 284 static int 285 ndmp_set_config(int argc, char **argv, ndmp_command_t *cur_cmd) 286 { 287 int c, i; 288 289 if (argc < 2) { 290 (void) fprintf(stderr, gettext("Missing property=value " 291 "argument\n")); 292 usage(B_FALSE, cur_cmd); 293 } 294 while ((c = getopt(argc, argv, ":p:")) != -1) { 295 switch (c) { 296 case 'p': 297 ndmp_set_config_process(optarg); 298 break; 299 case ':': 300 (void) fprintf(stderr, gettext("Option -%c " 301 "requires an operand\n"), optopt); 302 break; 303 case '?': 304 (void) fprintf(stderr, gettext("Unrecognized " 305 "option: -%c\n"), optopt); 306 } 307 } 308 /* 309 * optind is initialized to 1 if the -p option is not used, 310 * otherwise index to argv. 311 */ 312 argc -= optind; 313 argv += optind; 314 315 for (i = 0; i < argc; i++) { 316 if (strncmp(argv[i], "-p", 2) == 0) 317 continue; 318 319 ndmp_set_config_process(argv[i]); 320 } 321 return (0); 322 } 323 324 static void 325 ndmp_set_config_process(char *propname) 326 { 327 char *propvalue; 328 int ret, j; 329 330 if ((propvalue = strchr(propname, '=')) == NULL) { 331 (void) fprintf(stderr, gettext("Missing value in " 332 "property=value argument for %s\n"), propname); 333 return; 334 } 335 *propvalue = '\0'; 336 propvalue++; 337 338 if (*propname == '\0') { 339 (void) fprintf(stderr, gettext("Missing property in " 340 "property=value argument for %s\n"), propname); 341 return; 342 } 343 for (j = 0; j < NDMPADM_NPROP; j++) { 344 if (strcmp(propname, prop_table[j]) == 0) 345 break; 346 } 347 if (j == NDMPADM_NPROP) { 348 (void) fprintf(stdout, gettext("%s is invalid property or " 349 "variable\n"), propname); 350 return; 351 } 352 ret = ndmp_set_prop(propname, propvalue); 353 if (ret != -1) { 354 if (!ndmp_door_status()) { 355 if (ndmp_service_refresh() == -1) 356 (void) fprintf(stdout, gettext("Could not " 357 "refesh property of service ndmpd\n")); 358 } 359 } else { 360 (void) fprintf(stdout, gettext("Could not set property for " 361 "%s - %s\n"), propname, ndmp_strerror(ndmp_errno)); 362 } 363 } 364 365 /*ARGSUSED*/ 366 static int 367 ndmp_show_devices(int argc, char **argv, ndmp_command_t *cur_cmd) 368 { 369 int ret; 370 ndmp_devinfo_t *dip = NULL; 371 size_t size; 372 373 if (ndmp_door_status()) { 374 (void) fprintf(stdout, 375 gettext("Service ndmpd not running\n")); 376 return (-1); 377 } 378 379 ret = ndmp_get_devinfo(&dip, &size); 380 381 if (ret == -1) 382 (void) fprintf(stdout, 383 gettext("Could not get device information\n")); 384 else 385 ndmp_devinfo_print(dip, size); 386 387 ndmp_get_devinfo_free(dip, size); 388 return (0); 389 } 390 391 static int 392 ndmp_show_sessions(int argc, char **argv, ndmp_command_t *cur_cmd) 393 { 394 ndmp_session_info_t *sinfo = NULL; 395 ndmp_session_info_t *sp = NULL; 396 uint_t num; 397 int c, ret, i, j; 398 int statarg = 0; 399 char *value; 400 char *type_subopts[] = { "tape", "scsi", "data", "mover", NULL }; 401 402 if (ndmp_door_status()) { 403 (void) fprintf(stdout, 404 gettext("Service ndmpd not running\n")); 405 return (-1); 406 } 407 408 /* Detail output if no option is specified */ 409 if (argc == 1) { 410 statarg = NDMP_CAT_ALL; 411 } else { 412 statarg = 0; 413 while ((c = getopt(argc, argv, ":i:")) != -1) { 414 switch (c) { 415 case 'i': 416 while (*optarg != '\0') { 417 switch (getsubopt(&optarg, type_subopts, 418 &value)) { 419 case 0: 420 statarg |= NDMP_CAT_TAPE; 421 break; 422 case 1: 423 statarg |= NDMP_CAT_SCSI; 424 break; 425 case 2: 426 statarg |= NDMP_CAT_DATA; 427 break; 428 case 3: 429 statarg |= NDMP_CAT_MOVER; 430 break; 431 default: 432 (void) fprintf(stderr, 433 gettext("Invalid object " 434 "type '%s'\n"), value); 435 usage(B_FALSE, cur_cmd); 436 } 437 } 438 break; 439 case ':': 440 (void) fprintf(stderr, 441 gettext("Missing argument for " 442 "'%c' option\n"), optopt); 443 usage(B_FALSE, cur_cmd); 444 break; 445 case '?': 446 (void) fprintf(stderr, 447 gettext("Invalid option '%c'\n"), optopt); 448 usage(B_FALSE, cur_cmd); 449 } 450 } 451 /* if -i and its argument are not specified, display all */ 452 if (statarg == 0) 453 statarg = NDMP_CAT_ALL; 454 } 455 /* 456 * optind is initialized to 1 if the -i option is not used, otherwise 457 * index to argv. 458 */ 459 argc -= optind; 460 argv += optind; 461 462 ret = ndmp_get_session_info(&sinfo, &num); 463 if (ret == -1) { 464 (void) fprintf(stdout, 465 gettext("Could not get session information\n")); 466 } else { 467 if (argc == 0) { 468 ndmp_session_all_print(statarg, sinfo, num); 469 } else { 470 for (i = 0; i < argc; i++) { 471 sp = sinfo; 472 for (j = 0; j < num; j++, sp++) { 473 if (sp->nsi_sid == atoi(argv[i])) { 474 ndmp_session_print(statarg, sp); 475 (void) fprintf(stdout, "\n"); 476 break; 477 } 478 } 479 if (j == num) { 480 (void) fprintf(stdout, 481 gettext("Session %d not " 482 "found\n"), atoi(argv[i])); 483 } 484 } 485 } 486 ndmp_get_session_info_free(sinfo, num); 487 } 488 return (0); 489 } 490 491 /*ARGSUSED*/ 492 static int 493 ndmp_kill_sessions(int argc, char **argv, ndmp_command_t *cur_cmd) 494 { 495 int ret, i; 496 497 if (ndmp_door_status()) { 498 (void) fprintf(stdout, 499 gettext("Service ndmpd not running.\n")); 500 return (-1); 501 } 502 503 /* If no arg is specified, print the usage and exit */ 504 if (argc == 1) 505 usage(B_FALSE, cur_cmd); 506 507 for (i = 1; i < argc; i++) { 508 if (atoi(argv[i]) > 0) { 509 ret = ndmp_terminate_session(atoi(argv[i])); 510 } else { 511 (void) fprintf(stderr, 512 gettext("Invalid argument %s\n"), argv[i]); 513 continue; 514 } 515 if (ret == -1) 516 (void) fprintf(stdout, 517 gettext("Session id %d not found.\n"), 518 atoi(argv[i])); 519 } 520 return (0); 521 } 522 523 static int 524 ndmp_get_password(char **password) 525 { 526 char *pw1, pw2[257]; 527 int i; 528 529 for (i = 0; i < NDMP_PASSWORD_RETRIES; i++) { 530 /* 531 * getpassphrase use the same buffer to return password, so 532 * copy the result in different buffer, before calling the 533 * getpassphrase again. 534 */ 535 if ((pw1 = 536 getpassphrase(gettext("Enter new password: "))) != NULL) { 537 (void) strlcpy(pw2, pw1, sizeof (pw2)); 538 if ((pw1 = 539 getpassphrase(gettext("Re-enter password: "))) 540 != NULL) { 541 if (strncmp(pw1, pw2, strlen(pw1)) == 0) { 542 *password = pw1; 543 return (0); 544 } else { 545 (void) fprintf(stderr, 546 gettext("Both password did not " 547 "match.\n")); 548 } 549 } 550 } 551 } 552 return (-1); 553 } 554 555 static int 556 ndmp_enable_auth(int argc, char **argv, ndmp_command_t *cur_cmd) 557 { 558 char *auth_type, *username, *password; 559 int c, i, auth_type_flag = 0; 560 char *enc_password; 561 562 /* enable <-a auth-type> <-u username> */ 563 if (argc != 5) { 564 usage(B_FALSE, cur_cmd); 565 } 566 567 while ((c = getopt(argc, argv, ":a:u:")) != -1) { 568 switch (c) { 569 case 'a': 570 auth_type = strdup(optarg); 571 break; 572 case 'u': 573 username = strdup(optarg); 574 break; 575 case ':': 576 (void) fprintf(stderr, gettext("Option -%c " 577 "requires an operand\n"), optopt); 578 usage(B_FALSE, cur_cmd); 579 break; 580 case '?': 581 (void) fprintf(stderr, gettext("Unrecognized " 582 "option: -%c\n"), optopt); 583 usage(B_FALSE, cur_cmd); 584 } 585 } 586 587 if ((auth_type) && (username)) { 588 if (ndmp_get_password(&password)) { 589 (void) fprintf(stderr, gettext("Could not get correct " 590 "password, exiting...")); 591 free(auth_type); 592 free(username); 593 exit(-1); 594 } 595 } else { 596 (void) fprintf(stderr, gettext("%s or %s can not be blank"), 597 "'auth-type'", "'username'"); 598 free(auth_type); 599 free(username); 600 exit(-1); 601 } 602 603 if ((enc_password = ndmp_base64_encode(password)) == NULL) { 604 (void) fprintf(stdout, 605 gettext("Could not encode password - %s\n"), 606 ndmp_strerror(ndmp_errno)); 607 free(auth_type); 608 free(username); 609 exit(-1); 610 } 611 612 for (i = 0; i < NAUTH; i++) { 613 if (strncmp(auth_type, ndmp_auth_table[i].auth_type, 614 strlen(ndmp_auth_table[i].auth_type)) == 0) { 615 auth_type_flag = 1; 616 if ((ndmp_set_prop((char *)ndmp_auth_table[i].username, 617 username)) == -1) { 618 (void) fprintf(stdout, 619 gettext("Could not set username - %s\n"), 620 ndmp_strerror(ndmp_errno)); 621 continue; 622 } 623 if ((ndmp_set_prop((char *)ndmp_auth_table[i].password, 624 enc_password)) == -1) { 625 (void) fprintf(stdout, 626 gettext("Could not set password - %s\n"), 627 ndmp_strerror(ndmp_errno)); 628 continue; 629 } 630 if (!ndmp_door_status() && 631 (ndmp_service_refresh()) == -1) { 632 (void) fprintf(stdout, 633 gettext("Could not refesh ndmpd service " 634 "properties\n")); 635 } 636 } 637 } 638 free(auth_type); 639 free(username); 640 free(enc_password); 641 642 if (!auth_type_flag) 643 usage(B_FALSE, cur_cmd); 644 645 return (0); 646 } 647 648 static int 649 ndmp_disable_auth(int argc, char **argv, ndmp_command_t *cur_cmd) 650 { 651 char *auth_type; 652 int c, i, auth_type_flag = 0; 653 654 /* disable <-a auth-type> */ 655 if (argc != 3) { 656 usage(B_FALSE, cur_cmd); 657 } 658 659 while ((c = getopt(argc, argv, ":a:")) != -1) { 660 switch (c) { 661 case 'a': 662 auth_type = strdup(optarg); 663 break; 664 case ':': 665 (void) fprintf(stderr, gettext("Option -%c " 666 "requires an operand\n"), optopt); 667 break; 668 case '?': 669 (void) fprintf(stderr, gettext("Unrecognized " 670 "option: -%c\n"), optopt); 671 } 672 } 673 for (i = 0; i < NAUTH; i++) { 674 if (strncmp(auth_type, ndmp_auth_table[i].auth_type, 675 strlen(ndmp_auth_table[i].auth_type)) == 0) { 676 auth_type_flag = 1; 677 if ((ndmp_set_prop((char *)ndmp_auth_table[i].username, 678 "")) == -1) { 679 (void) fprintf(stdout, 680 gettext("Could not clear username - %s\n"), 681 ndmp_strerror(ndmp_errno)); 682 continue; 683 } 684 if ((ndmp_set_prop((char *)ndmp_auth_table[i].password, 685 "")) == -1) { 686 (void) fprintf(stdout, 687 gettext("Could not clear password - %s\n"), 688 ndmp_strerror(ndmp_errno)); 689 continue; 690 } 691 if (!ndmp_door_status() && 692 (ndmp_service_refresh()) == -1) { 693 (void) fprintf(stdout, gettext("Could not " 694 "refesh ndmpd service properties\n")); 695 } 696 } 697 } 698 free(auth_type); 699 700 if (!auth_type_flag) 701 usage(B_FALSE, cur_cmd); 702 703 return (0); 704 } 705 706 int 707 main(int argc, char **argv) 708 { 709 int ret; 710 int i; 711 char *cmdname; 712 ndmp_command_t *current_command = NULL; 713 714 (void) setlocale(LC_ALL, ""); 715 (void) textdomain(TEXT_DOMAIN); 716 717 opterr = 0; 718 719 /* Make sure the user has specified some command. */ 720 if (argc < 2) { 721 (void) fprintf(stderr, gettext("Missing command.\n")); 722 usage(B_FALSE, current_command); 723 } 724 725 cmdname = argv[1]; 726 727 /* 728 * Special case '-?' 729 */ 730 if (strcmp(cmdname, "-?") == 0) 731 usage(B_TRUE, current_command); 732 733 /* 734 * Run the appropriate sub-command. 735 */ 736 for (i = 0; i < NCOMMAND; i++) { 737 if (strcmp(cmdname, command_table[i].nc_name) == 0) { 738 current_command = &command_table[i]; 739 ret = command_table[i].func(argc - 1, argv + 1, 740 current_command); 741 break; 742 } 743 } 744 745 if (i == NCOMMAND) { 746 (void) fprintf(stderr, gettext("Unrecognized " 747 "command '%s'\n"), cmdname); 748 usage(B_FALSE, current_command); 749 } 750 751 return (ret); 752 } 753