ndmpadm_main.c revision 1e05b03fa76ee89d509f0c461b36cb865f1e6794
199767f8919635c4928607450d9e0abb932109ceToomas Soome/*
199767f8919635c4928607450d9e0abb932109ceToomas Soome * Copyright (c) 2008, 2010, Oracle and/or its affiliates. All rights reserved.
199767f8919635c4928607450d9e0abb932109ceToomas Soome */
199767f8919635c4928607450d9e0abb932109ceToomas Soome
199767f8919635c4928607450d9e0abb932109ceToomas Soome/*
199767f8919635c4928607450d9e0abb932109ceToomas Soome * BSD 3 Clause License
199767f8919635c4928607450d9e0abb932109ceToomas Soome *
199767f8919635c4928607450d9e0abb932109ceToomas Soome * Copyright (c) 2007, The Storage Networking Industry Association.
199767f8919635c4928607450d9e0abb932109ceToomas Soome *
199767f8919635c4928607450d9e0abb932109ceToomas Soome * Redistribution and use in source and binary forms, with or without
199767f8919635c4928607450d9e0abb932109ceToomas Soome * modification, are permitted provided that the following conditions
199767f8919635c4928607450d9e0abb932109ceToomas Soome * are met:
199767f8919635c4928607450d9e0abb932109ceToomas Soome * - Redistributions of source code must retain the above copyright
199767f8919635c4928607450d9e0abb932109ceToomas Soome * notice, this list of conditions and the following disclaimer.
199767f8919635c4928607450d9e0abb932109ceToomas Soome *
199767f8919635c4928607450d9e0abb932109ceToomas Soome * - Redistributions in binary form must reproduce the above copyright
199767f8919635c4928607450d9e0abb932109ceToomas Soome * notice, this list of conditions and the following disclaimer in
199767f8919635c4928607450d9e0abb932109ceToomas Soome * the documentation and/or other materials provided with the
199767f8919635c4928607450d9e0abb932109ceToomas Soome * distribution.
199767f8919635c4928607450d9e0abb932109ceToomas Soome *
199767f8919635c4928607450d9e0abb932109ceToomas Soome * - Neither the name of The Storage Networking Industry Association (SNIA)
199767f8919635c4928607450d9e0abb932109ceToomas Soome * nor the names of its contributors may be used to endorse or promote
199767f8919635c4928607450d9e0abb932109ceToomas Soome * products derived from this software without specific prior written
199767f8919635c4928607450d9e0abb932109ceToomas Soome * permission.
199767f8919635c4928607450d9e0abb932109ceToomas Soome *
199767f8919635c4928607450d9e0abb932109ceToomas Soome * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
199767f8919635c4928607450d9e0abb932109ceToomas Soome * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
199767f8919635c4928607450d9e0abb932109ceToomas Soome * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
199767f8919635c4928607450d9e0abb932109ceToomas Soome * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
199767f8919635c4928607450d9e0abb932109ceToomas Soome * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
199767f8919635c4928607450d9e0abb932109ceToomas Soome * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
199767f8919635c4928607450d9e0abb932109ceToomas Soome * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
199767f8919635c4928607450d9e0abb932109ceToomas Soome * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
199767f8919635c4928607450d9e0abb932109ceToomas Soome * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
199767f8919635c4928607450d9e0abb932109ceToomas Soome * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
199767f8919635c4928607450d9e0abb932109ceToomas Soome * POSSIBILITY OF SUCH DAMAGE.
199767f8919635c4928607450d9e0abb932109ceToomas Soome */
199767f8919635c4928607450d9e0abb932109ceToomas Soome#include <assert.h>
199767f8919635c4928607450d9e0abb932109ceToomas Soome#include <ctype.h>
199767f8919635c4928607450d9e0abb932109ceToomas Soome#include <libgen.h>
199767f8919635c4928607450d9e0abb932109ceToomas Soome#include <libintl.h>
199767f8919635c4928607450d9e0abb932109ceToomas Soome#include <locale.h>
199767f8919635c4928607450d9e0abb932109ceToomas Soome#include <stddef.h>
199767f8919635c4928607450d9e0abb932109ceToomas Soome#include <stdio.h>
199767f8919635c4928607450d9e0abb932109ceToomas Soome#include <stdlib.h>
199767f8919635c4928607450d9e0abb932109ceToomas Soome#include <strings.h>
199767f8919635c4928607450d9e0abb932109ceToomas Soome#include <unistd.h>
199767f8919635c4928607450d9e0abb932109ceToomas Soome#include <fcntl.h>
199767f8919635c4928607450d9e0abb932109ceToomas Soome#include <sys/stat.h>
199767f8919635c4928607450d9e0abb932109ceToomas Soome#include <door.h>
199767f8919635c4928607450d9e0abb932109ceToomas Soome#include <sys/mman.h>
199767f8919635c4928607450d9e0abb932109ceToomas Soome#include <libndmp.h>
#include "ndmpadm.h"
typedef enum {
HELP_GET_CONFIG,
HELP_SET_CONFIG,
HELP_SHOW_DEVICES,
HELP_SHOW_SESSIONS,
HELP_KILL_SESSIONS,
HELP_ENABLE_AUTH,
HELP_DISABLE_AUTH
} ndmp_help_t;
typedef struct ndmp_command {
const char *nc_name;
int (*func)(int argc, char **argv,
struct ndmp_command *cur_cmd);
ndmp_help_t nc_usage;
} ndmp_command_t;
static int ndmp_get_config(int, char **, ndmp_command_t *);
static int ndmp_set_config(int, char **, ndmp_command_t *);
static int ndmp_show_devices(int, char **, ndmp_command_t *);
static int ndmp_show_sessions(int, char **, ndmp_command_t *);
static int ndmp_kill_sessions(int, char **, ndmp_command_t *);
static int ndmp_enable_auth(int, char **, ndmp_command_t *);
static int ndmp_disable_auth(int, char **, ndmp_command_t *);
static void ndmp_get_config_process(char *);
static void ndmp_set_config_process(char *arg);
static int ndmp_get_password(char **);
static ndmp_command_t command_table[] = {
{ "get", ndmp_get_config, HELP_GET_CONFIG },
{ "set", ndmp_set_config, HELP_SET_CONFIG },
{ "show-devices", ndmp_show_devices, HELP_SHOW_DEVICES },
{ "show-sessions", ndmp_show_sessions, HELP_SHOW_SESSIONS },
{ "kill-sessions", ndmp_kill_sessions, HELP_KILL_SESSIONS },
{ "enable", ndmp_enable_auth, HELP_ENABLE_AUTH },
{ "disable", ndmp_disable_auth, HELP_DISABLE_AUTH }
};
#define NCOMMAND (sizeof (command_table) / sizeof (command_table[0]))
static char *prop_table[] = {
"debug-path",
"dump-pathnode",
"tar-pathnode",
"ignore-ctime",
"token-maxseq",
"version",
"dar-support",
"tcp-port",
"backup-quarantine",
"restore-quarantine",
"overwrite-quarantine",
"zfs-force-override",
"drive-type"
};
#define NDMPADM_NPROP (sizeof (prop_table) / sizeof (prop_table[0]))
typedef struct ndmp_auth {
const char *auth_type;
const char *username;
const char *password;
} ndmp_auth_t;
static ndmp_auth_t ndmp_auth_table[] = {
{ "cram-md5", "cram-md5-username", "cram-md5-password" },
{ "cleartext", "cleartext-username", "cleartext-password" }
};
#define NAUTH (sizeof (ndmp_auth_table) / sizeof (ndmp_auth_table[0]))
#define NDMP_PASSWORD_RETRIES 3
#if !defined(TEXT_DOMAIN)
#define TEXT_DOMAIN "SYS_TEST"
#endif
static const char *
get_usage(ndmp_help_t idx)
{
switch (idx) {
case HELP_SET_CONFIG:
return ("\tset [-p] <property=value> [[-p] property=value] "
"...\n");
case HELP_GET_CONFIG:
return ("\tget [-p] [property] [[-p] property] ...\n");
case HELP_SHOW_DEVICES:
return ("\tshow-devices\n");
case HELP_SHOW_SESSIONS:
return ("\tshow-sessions [-i tape,scsi,data,mover] [id] ...\n");
case HELP_KILL_SESSIONS:
return ("\tkill-sessions <id ...>\n");
case HELP_ENABLE_AUTH:
return ("\tenable <-a auth-type> <-u username>\n");
case HELP_DISABLE_AUTH:
return ("\tdisable <-a auth-type>\n");
}
return (NULL);
}
/*
* Display usage message. If we're inside a command, display only the usage for
* that command. Otherwise, iterate over the entire command table and display
* a complete usage message.
*/
static void
usage(boolean_t requested, ndmp_command_t *current_command)
{
int i;
boolean_t show_properties = B_FALSE;
FILE *fp = requested ? stdout : stderr;
if (current_command == NULL) {
(void) fprintf(fp,
gettext("Usage: ndmpadm subcommand args ...\n"));
(void) fprintf(fp,
gettext("where 'command' is one of the following:\n\n"));
for (i = 0; i < NCOMMAND; i++) {
(void) fprintf(fp, "%s",
get_usage(command_table[i].nc_usage));
}
(void) fprintf(fp, gettext("\t\twhere %s can be either "
"%s or %s\n"), "'auth-type'", "'cram-md5'", "'cleartext'");
} else {
(void) fprintf(fp, gettext("Usage:\n"));
(void) fprintf(fp, "%s", get_usage(current_command->nc_usage));
if ((current_command->nc_usage == HELP_ENABLE_AUTH) ||
(current_command->nc_usage == HELP_DISABLE_AUTH))
(void) fprintf(fp, gettext("\t\twhere %s can be either "
"%s or %s\n"),
"'auth-type'", "'cram-md5'", "'cleartext'");
}
if (current_command != NULL &&
(strcmp(current_command->nc_name, "set") == 0))
show_properties = B_TRUE;
if (show_properties) {
(void) fprintf(fp,
gettext("\nThe following properties are supported:\n"));
(void) fprintf(fp, gettext("\n\tPROPERTY"));
(void) fprintf(fp, "\n\t%s", "-------------");
for (i = 0; i < NDMPADM_NPROP; i++)
(void) fprintf(fp, "\n\t%s", prop_table[i]);
(void) fprintf(fp, "\n");
}
exit(requested ? 0 : 2);
}
/*ARGSUSED*/
static int
ndmp_get_config(int argc, char **argv, ndmp_command_t *cur_cmd)
{
char *propval;
int i, c;
if (argc == 1) {
/*
* Get all the properties and variables ndmpadm is allowed
* to see.
*/
for (i = 0; i < NDMPADM_NPROP; i++) {
if (ndmp_get_prop(prop_table[i], &propval)) {
(void) fprintf(stdout, "\t%s=\n",
prop_table[i]);
} else {
(void) fprintf(stdout, "\t%s=%s\n",
prop_table[i], propval);
free(propval);
}
}
} else if (argc > 1) {
while ((c = getopt(argc, argv, ":p:")) != -1) {
switch (c) {
case 'p':
ndmp_get_config_process(optarg);
break;
case ':':
(void) fprintf(stderr, gettext("Option -%c "
"requires an operand\n"), optopt);
break;
case '?':
(void) fprintf(stderr, gettext("Unrecognized "
"option: -%c\n"), optopt);
}
}
/*
* optind is initialized to 1 if the -p option is not used,
* otherwise index to argv.
*/
argc -= optind;
argv += optind;
for (i = 0; i < argc; i++) {
if (strncmp(argv[i], "-p", 2) == 0)
continue;
ndmp_get_config_process(argv[i]);
}
}
return (0);
}
static void
ndmp_get_config_process(char *arg)
{
int j;
char *propval;
for (j = 0; j < NDMPADM_NPROP; j++) {
if (strcmp(arg, prop_table[j]) == 0) {
if (ndmp_get_prop(arg, &propval)) {
(void) fprintf(stdout, "\t%s=\n", arg);
} else {
(void) fprintf(stdout, "\t%s=%s\n",
arg, propval);
free(propval);
}
break;
}
}
if (j == NDMPADM_NPROP) {
(void) fprintf(stdout, gettext("\t%s is invalid property "
"or variable\n"), arg);
}
}
/*ARGSUSED*/
static int
ndmp_set_config(int argc, char **argv, ndmp_command_t *cur_cmd)
{
int c, i;
if (argc < 2) {
(void) fprintf(stderr, gettext("Missing property=value "
"argument\n"));
usage(B_FALSE, cur_cmd);
}
while ((c = getopt(argc, argv, ":p:")) != -1) {
switch (c) {
case 'p':
ndmp_set_config_process(optarg);
break;
case ':':
(void) fprintf(stderr, gettext("Option -%c "
"requires an operand\n"), optopt);
break;
case '?':
(void) fprintf(stderr, gettext("Unrecognized "
"option: -%c\n"), optopt);
}
}
/*
* optind is initialized to 1 if the -p option is not used,
* otherwise index to argv.
*/
argc -= optind;
argv += optind;
for (i = 0; i < argc; i++) {
if (strncmp(argv[i], "-p", 2) == 0)
continue;
ndmp_set_config_process(argv[i]);
}
return (0);
}
static void
ndmp_set_config_process(char *propname)
{
char *propvalue;
int ret, j;
if ((propvalue = strchr(propname, '=')) == NULL) {
(void) fprintf(stderr, gettext("Missing value in "
"property=value argument for %s\n"), propname);
return;
}
*propvalue = '\0';
propvalue++;
if (*propname == '\0') {
(void) fprintf(stderr, gettext("Missing property in "
"property=value argument for %s\n"), propname);
return;
}
for (j = 0; j < NDMPADM_NPROP; j++) {
if (strcmp(propname, prop_table[j]) == 0)
break;
}
if (j == NDMPADM_NPROP) {
(void) fprintf(stdout, gettext("%s is invalid property or "
"variable\n"), propname);
return;
}
ret = ndmp_set_prop(propname, propvalue);
if (ret != -1) {
if (!ndmp_door_status()) {
if (ndmp_service_refresh() == -1)
(void) fprintf(stdout, gettext("Could not "
"refesh property of service ndmpd\n"));
}
} else {
(void) fprintf(stdout, gettext("Could not set property for "
"%s - %s\n"), propname, ndmp_strerror(ndmp_errno));
}
}
/*ARGSUSED*/
static int
ndmp_show_devices(int argc, char **argv, ndmp_command_t *cur_cmd)
{
int ret;
ndmp_devinfo_t *dip = NULL;
size_t size;
if (ndmp_door_status()) {
(void) fprintf(stdout,
gettext("Service ndmpd not running\n"));
return (-1);
}
ret = ndmp_get_devinfo(&dip, &size);
if (ret == -1)
(void) fprintf(stdout,
gettext("Could not get device information\n"));
else
ndmp_devinfo_print(dip, size);
ndmp_get_devinfo_free(dip, size);
return (0);
}
static int
ndmp_show_sessions(int argc, char **argv, ndmp_command_t *cur_cmd)
{
ndmp_session_info_t *sinfo = NULL;
ndmp_session_info_t *sp = NULL;
uint_t num;
int c, ret, i, j;
int statarg = 0;
char *value;
char *type_subopts[] = { "tape", "scsi", "data", "mover", NULL };
if (ndmp_door_status()) {
(void) fprintf(stdout,
gettext("Service ndmpd not running\n"));
return (-1);
}
/* Detail output if no option is specified */
if (argc == 1) {
statarg = NDMP_CAT_ALL;
} else {
statarg = 0;
while ((c = getopt(argc, argv, ":i:")) != -1) {
switch (c) {
case 'i':
while (*optarg != '\0') {
switch (getsubopt(&optarg, type_subopts,
&value)) {
case 0:
statarg |= NDMP_CAT_TAPE;
break;
case 1:
statarg |= NDMP_CAT_SCSI;
break;
case 2:
statarg |= NDMP_CAT_DATA;
break;
case 3:
statarg |= NDMP_CAT_MOVER;
break;
default:
(void) fprintf(stderr,
gettext("Invalid object "
"type '%s'\n"), value);
usage(B_FALSE, cur_cmd);
}
}
break;
case ':':
(void) fprintf(stderr,
gettext("Missing argument for "
"'%c' option\n"), optopt);
usage(B_FALSE, cur_cmd);
break;
case '?':
(void) fprintf(stderr,
gettext("Invalid option '%c'\n"), optopt);
usage(B_FALSE, cur_cmd);
}
}
/* if -i and its argument are not specified, display all */
if (statarg == 0)
statarg = NDMP_CAT_ALL;
}
/*
* optind is initialized to 1 if the -i option is not used, otherwise
* index to argv.
*/
argc -= optind;
argv += optind;
ret = ndmp_get_session_info(&sinfo, &num);
if (ret == -1) {
(void) fprintf(stdout,
gettext("Could not get session information\n"));
} else {
if (argc == 0) {
ndmp_session_all_print(statarg, sinfo, num);
} else {
for (i = 0; i < argc; i++) {
sp = sinfo;
for (j = 0; j < num; j++, sp++) {
if (sp->nsi_sid == atoi(argv[i])) {
ndmp_session_print(statarg, sp);
(void) fprintf(stdout, "\n");
break;
}
}
if (j == num) {
(void) fprintf(stdout,
gettext("Session %d not "
"found\n"), atoi(argv[i]));
}
}
}
ndmp_get_session_info_free(sinfo, num);
}
return (0);
}
/*ARGSUSED*/
static int
ndmp_kill_sessions(int argc, char **argv, ndmp_command_t *cur_cmd)
{
int ret, i;
if (ndmp_door_status()) {
(void) fprintf(stdout,
gettext("Service ndmpd not running.\n"));
return (-1);
}
/* If no arg is specified, print the usage and exit */
if (argc == 1)
usage(B_FALSE, cur_cmd);
for (i = 1; i < argc; i++) {
if (atoi(argv[i]) > 0) {
ret = ndmp_terminate_session(atoi(argv[i]));
} else {
(void) fprintf(stderr,
gettext("Invalid argument %s\n"), argv[i]);
continue;
}
if (ret == -1)
(void) fprintf(stdout,
gettext("Session id %d not found.\n"),
atoi(argv[i]));
}
return (0);
}
static int
ndmp_get_password(char **password)
{
char *pw1, pw2[257];
int i;
for (i = 0; i < NDMP_PASSWORD_RETRIES; i++) {
/*
* getpassphrase use the same buffer to return password, so
* copy the result in different buffer, before calling the
* getpassphrase again.
*/
if ((pw1 =
getpassphrase(gettext("Enter new password: "))) != NULL) {
(void) strlcpy(pw2, pw1, sizeof (pw2));
if ((pw1 =
getpassphrase(gettext("Re-enter password: ")))
!= NULL) {
if (strncmp(pw1, pw2, strlen(pw1)) == 0) {
*password = pw1;
return (0);
} else {
(void) fprintf(stderr,
gettext("Both password did not "
"match.\n"));
}
}
}
}
return (-1);
}
static int
ndmp_enable_auth(int argc, char **argv, ndmp_command_t *cur_cmd)
{
char *auth_type, *username, *password;
int c, i, auth_type_flag = 0;
char *enc_password;
/* enable <-a auth-type> <-u username> */
if (argc != 5) {
usage(B_FALSE, cur_cmd);
}
while ((c = getopt(argc, argv, ":a:u:")) != -1) {
switch (c) {
case 'a':
auth_type = strdup(optarg);
break;
case 'u':
username = strdup(optarg);
break;
case ':':
(void) fprintf(stderr, gettext("Option -%c "
"requires an operand\n"), optopt);
usage(B_FALSE, cur_cmd);
break;
case '?':
(void) fprintf(stderr, gettext("Unrecognized "
"option: -%c\n"), optopt);
usage(B_FALSE, cur_cmd);
}
}
if ((auth_type) && (username)) {
if (ndmp_get_password(&password)) {
(void) fprintf(stderr, gettext("Could not get correct "
"password, exiting..."));
free(auth_type);
free(username);
exit(-1);
}
} else {
(void) fprintf(stderr, gettext("%s or %s can not be blank"),
"'auth-type'", "'username'");
free(auth_type);
free(username);
exit(-1);
}
if ((enc_password = ndmp_base64_encode(password)) == NULL) {
(void) fprintf(stdout,
gettext("Could not encode password - %s\n"),
ndmp_strerror(ndmp_errno));
free(auth_type);
free(username);
exit(-1);
}
for (i = 0; i < NAUTH; i++) {
if (strncmp(auth_type, ndmp_auth_table[i].auth_type,
strlen(ndmp_auth_table[i].auth_type)) == 0) {
auth_type_flag = 1;
if ((ndmp_set_prop((char *)ndmp_auth_table[i].username,
username)) == -1) {
(void) fprintf(stdout,
gettext("Could not set username - %s\n"),
ndmp_strerror(ndmp_errno));
continue;
}
if ((ndmp_set_prop((char *)ndmp_auth_table[i].password,
enc_password)) == -1) {
(void) fprintf(stdout,
gettext("Could not set password - %s\n"),
ndmp_strerror(ndmp_errno));
continue;
}
if (!ndmp_door_status() &&
(ndmp_service_refresh()) == -1) {
(void) fprintf(stdout,
gettext("Could not refesh ndmpd service "
"properties\n"));
}
}
}
free(auth_type);
free(username);
free(enc_password);
if (!auth_type_flag)
usage(B_FALSE, cur_cmd);
return (0);
}
static int
ndmp_disable_auth(int argc, char **argv, ndmp_command_t *cur_cmd)
{
char *auth_type;
int c, i, auth_type_flag = 0;
/* disable <-a auth-type> */
if (argc != 3) {
usage(B_FALSE, cur_cmd);
}
while ((c = getopt(argc, argv, ":a:")) != -1) {
switch (c) {
case 'a':
auth_type = strdup(optarg);
break;
case ':':
(void) fprintf(stderr, gettext("Option -%c "
"requires an operand\n"), optopt);
break;
case '?':
(void) fprintf(stderr, gettext("Unrecognized "
"option: -%c\n"), optopt);
}
}
for (i = 0; i < NAUTH; i++) {
if (strncmp(auth_type, ndmp_auth_table[i].auth_type,
strlen(ndmp_auth_table[i].auth_type)) == 0) {
auth_type_flag = 1;
if ((ndmp_set_prop((char *)ndmp_auth_table[i].username,
"")) == -1) {
(void) fprintf(stdout,
gettext("Could not clear username - %s\n"),
ndmp_strerror(ndmp_errno));
continue;
}
if ((ndmp_set_prop((char *)ndmp_auth_table[i].password,
"")) == -1) {
(void) fprintf(stdout,
gettext("Could not clear password - %s\n"),
ndmp_strerror(ndmp_errno));
continue;
}
if (!ndmp_door_status() &&
(ndmp_service_refresh()) == -1) {
(void) fprintf(stdout, gettext("Could not "
"refesh ndmpd service properties\n"));
}
}
}
free(auth_type);
if (!auth_type_flag)
usage(B_FALSE, cur_cmd);
return (0);
}
int
main(int argc, char **argv)
{
int ret;
int i;
char *cmdname;
ndmp_command_t *current_command = NULL;
(void) setlocale(LC_ALL, "");
(void) textdomain(TEXT_DOMAIN);
opterr = 0;
/* Make sure the user has specified some command. */
if (argc < 2) {
(void) fprintf(stderr, gettext("Missing command.\n"));
usage(B_FALSE, current_command);
}
cmdname = argv[1];
/*
* Special case '-?'
*/
if (strcmp(cmdname, "-?") == 0)
usage(B_TRUE, current_command);
/*
* Run the appropriate sub-command.
*/
for (i = 0; i < NCOMMAND; i++) {
if (strcmp(cmdname, command_table[i].nc_name) == 0) {
current_command = &command_table[i];
ret = command_table[i].func(argc - 1, argv + 1,
current_command);
break;
}
}
if (i == NCOMMAND) {
(void) fprintf(stderr, gettext("Unrecognized "
"command '%s'\n"), cmdname);
usage(B_FALSE, current_command);
}
return (ret);
}