OpenSolaris_b135/cmd/iscsi/iscsitadm/main.c

Compare this file to the similar file:
Show the results in this format:

/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */

/*
 * Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

#include <stdlib.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/param.h>
#include <unistd.h>
#include <libintl.h>
#include <limits.h>
#include <string.h>
#include <syslog.h>
#include <errno.h>
#include <sys/stat.h>
#include <zone.h>
#include <netdb.h>

#include <iscsitgt_impl.h>
#include "cmdparse.h"
#include "helper.h"

#define	CREATE	SUBCOMMAND(0)
#define	LIST	SUBCOMMAND(1)
#define	MODIFY	SUBCOMMAND(2)
#define	DELETE	SUBCOMMAND(3)
#define	SHOW	SUBCOMMAND(4)

#define	TARGET		OBJECT(0)
#define	INITIATOR	OBJECT(1)
#define	ADMIN		OBJECT(2)
#define	TPGT		OBJECT(3)
#define	STATS		OBJECT(4)

#define	VERSION_STRING_MAX_LEN	10
#define	MAX_IPADDRESS_LEN	128

/*
 * Version number:
 *  MAJOR - This should only change when there is an incompatible change made
 *  to the interfaces or the output.
 *
 *  MINOR - This should change whenever there is a new command or new feature
 *  with no incompatible change.
 */
#define	VERSION_STRING_MAJOR	    "1"
#define	VERSION_STRING_MINOR	    "0"

#define	OPT_ENABLE	"enable"
#define	OPT_DISABLE	"disable"
#define	OPT_TRUE	"true"
#define	OPT_FALSE	"false"

/* subcommand functions */
static int createFunc(int, char **, int, cmdOptions_t *, void *);
static int listFunc(int, char **, int, cmdOptions_t *, void *);
static int modifyFunc(int, char **, int, cmdOptions_t *, void *);
static int deleteFunc(int, char **, int, cmdOptions_t *, void *);
static int showFunc(int, char **, int, cmdOptions_t *, void *);

/* object functions per subcommand */
static int createTarget(int, char *[], cmdOptions_t *);
static int createInitiator(int, char *[], cmdOptions_t *);
static int createTpgt(int, char *[], cmdOptions_t *);
static int modifyTarget(int, char *[], cmdOptions_t *);
static int modifyInitiator(int, char *[], cmdOptions_t *);
static int modifyTpgt(int, char *[], cmdOptions_t *);
static int modifyAdmin(int, char *[], cmdOptions_t *);
static int deleteTarget(int, char *[], cmdOptions_t *);
static int deleteInitiator(int, char *[], cmdOptions_t *);
static int deleteTpgt(int, char *[], cmdOptions_t *);
static int listTarget(int, char *[], cmdOptions_t *);
static int listInitiator(int, char *[], cmdOptions_t *);
static int listTpgt(int, char *[], cmdOptions_t *);
static int showAdmin(int, char *[], cmdOptions_t *);
static int showStats(int, char *[], cmdOptions_t *);

/* globals */
char *cmdName;

/*
 * Add new options here
 */
optionTbl_t longOptions[] = {
	{"size", required_arg, 'z', "size k/m/g/t"},
	{"type", required_arg, 't', "disk/tape/osd/raw"},
	{"lun", required_arg, 'u', "number"},
	{"alias", required_arg, 'a', "value"},
	{"backing-store", required_arg, 'b', "pathname"},
	{"tpgt", required_arg, 'p', "tpgt number"},
	{"acl", required_arg, 'l', "local initiator"},
	{"maxrecv", required_arg, 'm', "max recv data segment length"},
	{"chap-secret", no_arg, 'C', NULL},
	{"chap-name", required_arg, 'H', "chap username"},
	{"iqn", required_arg, 'n', "iSCSI node name"},
	{"ip-address", required_arg, 'i', "ip address"},
	{"base-directory", required_arg, 'd', "directory"},
	{"radius-access", required_arg, 'R', "enable/disable"},
	{"radius-server", required_arg, 'r', "hostname[:port]"},
	{"radius-secret", no_arg, 'P', NULL},
	{"isns-access", required_arg, 'S', "enable/disable"},
	{"isns-server", required_arg, 's', "hostname[:port]"},
	{"fast-write-ack", required_arg, 'f', "enable/disable"},
	{"verbose", no_arg, 'v', NULL},
	{"interval", required_arg, 'I', "seconds"},
	{"count", required_arg, 'N', "number"},
	{"all", no_arg, 'A', NULL},
	{NULL, 0, 0, 0}
};

/*
 * Add new subcommands here
 */
subcommand_t subcommands[] = {
	{"create", CREATE, createFunc},
	{"list", LIST, listFunc},
	{"modify", MODIFY, modifyFunc},
	{"delete", DELETE, deleteFunc},
	{"show", SHOW, showFunc},
	{NULL, 0, NULL}
};

/*
 * Add objects here
 */
object_t objects[] = {
	{"target", TARGET},
	{"initiator", INITIATOR},
	{"admin", ADMIN},
	{"tpgt", TPGT},
	{"stats", STATS},
	{NULL, 0}
};

/*
 * Rules for subcommands and objects
 * ReqiredOp, OptioalOp, NoOp, InvalidOp, MultiOp
 */
objectRules_t objectRules[] = {
	/*
	 * create/modify/delete subcmd requires an operand
	 * list subcmd optionally requires an operand
	 * no subcmd requires no operand
	 * no subcmd is invalid for this operand
	 * no subcmd can accept multiple operands
	 */
	{TARGET, CREATE|MODIFY|DELETE, LIST, 0, SHOW, 0, "local-target"},
	/*
	 * create/modify/delete subcmd requires an operand
	 * list subcmd optionally requires an operand
	 * no subcmd requires no operand
	 * no subcmd is invalid for this operand
	 * no subcmd can accept multiple operands
	 */
	{INITIATOR, CREATE|MODIFY|DELETE, LIST, 0, SHOW, 0, "local-initiator"},
	/*
	 * no subcmd requires an operand
	 * no subcmd optionally requires an operand
	 * modify/list subcmd requires no operand
	 * create/delete subcmd are invlaid for this operand
	 * no subcmd can accept multiple operands
	 */
	{ADMIN, 0, 0, MODIFY|SHOW, CREATE|DELETE|LIST, 0, NULL},
	/*
	 * create/modify/delete subcmd requires an operand
	 * list subcmd optionally requires an operand
	 * no subcmd requires no operand
	 * no subcmd is invalid for this operand
	 * no subcmd can accept multiple operands
	 */
	{TPGT, CREATE|MODIFY|DELETE, LIST, 0, SHOW, 0, "local-tpgt"},
	/*
	 * no subcmd requires an operand
	 * list subcmd optionally requires an operand
	 * no subcmd requires no operand
	 * create/delete/modify subcmd are invalid for this operand
	 * no subcmd can accept multiple operands
	 */
	{STATS, 0, SHOW, 0, CREATE|MODIFY|DELETE|LIST, 0, "local-target"},
	{0, 0, 0, 0, 0, NULL}
};

/*
 * list of objects, subcommands, valid short options, required flag and
 * exclusive option string
 *
 * If it's not here, there are no options for that object.
 */
optionRules_t optionRules[] = {
	{TARGET, CREATE, "tuzab", B_TRUE, NULL},
	{TARGET, MODIFY, "plamzu", B_TRUE, NULL},
	{TARGET, DELETE, "ulp", B_TRUE, NULL},
	{TARGET, LIST,   "v", B_FALSE, NULL},
	{INITIATOR, CREATE, "n", B_TRUE, NULL},
	{INITIATOR, MODIFY, "CH", B_TRUE, NULL},
	{INITIATOR, DELETE, "A", B_TRUE, NULL},
	{INITIATOR, LIST,   "v", B_FALSE, NULL},
	{TPGT, MODIFY, "i", B_TRUE, NULL},
	{TPGT, DELETE, "Ai", B_TRUE, NULL},
	{TPGT, LIST,   "v", B_FALSE, NULL},
	{ADMIN, MODIFY, "dHCRrPSsf", B_TRUE, NULL},
	{STATS, SHOW, "IN", B_FALSE, NULL},
};



/*ARGSUSED*/
static int
createFunc(int operandLen, char *operand[], int object, cmdOptions_t *options,
    void *addArgs)
{
	int ret;

	switch (object) {
		case TARGET:
			ret = createTarget(operandLen, operand, options);
			break;
		case INITIATOR:
			ret = createInitiator(operandLen, operand, options);
			break;
		case TPGT:
			ret = createTpgt(operandLen, operand, options);
			break;
		default:
			(void) fprintf(stderr, "%s: %s\n",
			    cmdName, gettext("unknown object"));
			ret = 1;
			break;
	}
	return (ret);
}

/*ARGSUSED*/
static int
listFunc(int operandLen, char *operand[], int object, cmdOptions_t *options,
    void *addArgs)
{
	int ret;

	switch (object) {
		case TARGET:
			ret = listTarget(operandLen, operand, options);
			break;
		case INITIATOR:
			ret = listInitiator(operandLen, operand, options);
			break;
		case TPGT:
			ret = listTpgt(operandLen, operand, options);
			break;
		default:
			(void) fprintf(stderr, "%s: %s\n",
			    cmdName, gettext("unknown object"));
			ret = 1;
			break;
	}
	return (ret);
}

/*ARGSUSED*/
static int
showFunc(int operandLen, char *operand[], int object, cmdOptions_t *options,
    void *addArgs)
{
	int ret;

	switch (object) {
		case STATS:
			ret = showStats(operandLen, operand, options);
			break;
		case ADMIN:
			ret = showAdmin(operandLen, operand, options);
			break;
		default:
			(void) fprintf(stderr, "%s: %s\n",
			    cmdName, gettext("unknown object"));
			ret = 1;
			break;
	}
	return (ret);
}

/*ARGSUSED*/
static int
modifyFunc(int operandLen, char *operand[], int object, cmdOptions_t *options,
    void *addArgs)
{
	int ret;

	switch (object) {
		case TARGET:
			ret = modifyTarget(operandLen, operand, options);
			break;
		case INITIATOR:
			ret = modifyInitiator(operandLen, operand, options);
			break;
		case TPGT:
			ret = modifyTpgt(operandLen, operand, options);
			break;
		case ADMIN:
			ret = modifyAdmin(operandLen, operand, options);
			break;
		default:
			(void) fprintf(stderr, "%s: %s\n",
			    cmdName, gettext("unknown object"));
			ret = 1;
			break;
	}
	return (ret);
}

/*ARGSUSED*/
static int
deleteFunc(int operandLen, char *operand[], int object, cmdOptions_t *options,
    void *addArgs)
{
	int ret;

	switch (object) {
		case TARGET:
			ret = deleteTarget(operandLen, operand, options);
			break;
		case INITIATOR:
			ret = deleteInitiator(operandLen, operand, options);
			break;
		case TPGT:
			ret = deleteTpgt(operandLen, operand, options);
			break;
		default:
			(void) fprintf(stderr, "%s: %s\n",
			    cmdName, gettext("unknown object"));
			ret = 1;
			break;
	}
	return (ret);
}

static int
formatErrString(tgt_node_t *node)
{
	int	code	= 0;
	int	rtn	= 0;
	char	*msg	= NULL;

	if (node == NULL) {
		(void) fprintf(stderr, "%s: %s\n", cmdName,
		    gettext("Unable to contact target daemon"));
		return (1);
	}
	if ((strcmp(node->x_name, XML_ELEMENT_ERROR) == 0) &&
	    (tgt_find_value_int(node, XML_ELEMENT_CODE, &code) == B_TRUE) &&
	    (tgt_find_value_str(node, XML_ELEMENT_MESSAGE, &msg) == B_TRUE)) {

		/*
		 * 1000 is the success code, so we don't need to display
		 * the success message.
		 */
		if (code != 1000) {
			(void) fprintf(stderr, "%s: %s %s\n",
			    cmdName, gettext("Error"), msg);
			rtn = 1;
		}
	} else {
		(void) fprintf(stderr, "%s: %s\n", cmdName,
		    gettext("Bad XML response"));
		rtn = 1;
	}
	if (msg)
		free(msg);
	return (rtn);
}

/*ARGSUSED*/
static int
createTarget(int operandLen, char *operand[], cmdOptions_t *options)
{
	char		*first_str	= NULL;
	tgt_node_t	*node;
	cmdOptions_t	*optionList = options;

	if (operand == NULL)
		return (1);

	tgt_buf_add_tag(&first_str, "create", Tag_Start);
	tgt_buf_add_tag(&first_str, XML_ELEMENT_TARG, Tag_Start);
	tgt_buf_add(&first_str, XML_ELEMENT_NAME, operand[0]);

	for (; optionList->optval; optionList++) {
		switch (optionList->optval) {
			case 't': /* type */
				if ((strcmp(optionList->optarg, "disk")) &&
				    (strcmp(optionList->optarg, "tape")) &&
				    (strcmp(optionList->optarg, "raw")) &&
				    (strcmp(optionList->optarg, "osd"))) {
					(void) fprintf(stderr, "%s: %c: %s\n",
					    cmdName, optionList->optval,
					    gettext("unknown type"));
					free(first_str);
					return (1);
				} else {
					tgt_buf_add(&first_str,
					    XML_ELEMENT_TYPE,
					    optionList->optarg);
				}
				break;
			case 'z': /* size */
				tgt_buf_add(&first_str, XML_ELEMENT_SIZE,
				    optionList->optarg);
				break;
			case 'u': /* lun number */
				tgt_buf_add(&first_str, XML_ELEMENT_LUN,
				    optionList->optarg);
				break;
			case 'a': /* alias */
				tgt_buf_add(&first_str, XML_ELEMENT_ALIAS,
				    optionList->optarg);
				break;
			case 'b': /* backing store */
				tgt_buf_add(&first_str, XML_ELEMENT_BACK,
				    optionList->optarg);
				break;
			default:
				(void) fprintf(stderr, "%s: %c: %s\n",
				    cmdName, optionList->optval,
				    gettext("unknown option"));
				free(first_str);
				return (1);
		}
	}

	tgt_buf_add_tag(&first_str, XML_ELEMENT_TARG, Tag_End);
	tgt_buf_add_tag(&first_str, "create", Tag_End);

	node = tgt_door_call(first_str, 0);
	free(first_str);
	return (formatErrString(node));
}

/*ARGSUSED*/
static int
createInitiator(int operandLen, char *operand[], cmdOptions_t *options)
{
	char		*first_str	= NULL;
	tgt_node_t	*node;
	cmdOptions_t	*optionList	= options;

	if (operand == NULL)
		return (1);
	if (options == NULL)
		return (1);

	tgt_buf_add_tag(&first_str, "create", Tag_Start);
	tgt_buf_add_tag(&first_str, XML_ELEMENT_INIT, Tag_Start);
	tgt_buf_add(&first_str, XML_ELEMENT_NAME, operand[0]);

	switch (optionList->optval) {
	case 'n': /* iqn */
		tgt_buf_add(&first_str, XML_ELEMENT_INAME, optionList->optarg);
		break;
	default:
		(void) fprintf(stderr, "%s: %c: %s\n",
		    cmdName, optionList->optval,
		    gettext("unknown option"));
		free(first_str);
		return (1);
	}

	tgt_buf_add_tag(&first_str, XML_ELEMENT_INIT, Tag_End);
	tgt_buf_add_tag(&first_str, "create", Tag_End);

	node = tgt_door_call(first_str, 0);
	free(first_str);
	return (formatErrString(node));
}

/*ARGSUSED*/
static int
createTpgt(int operandLen, char *operand[], cmdOptions_t *options)
{
	char		*first_str	= NULL;
	tgt_node_t	*node;

	if (operand == NULL)
		return (1);

	tgt_buf_add_tag(&first_str, "create", Tag_Start);
	tgt_buf_add_tag(&first_str, XML_ELEMENT_TPGT, Tag_Start);
	tgt_buf_add(&first_str, XML_ELEMENT_NAME, operand[0]);
	tgt_buf_add_tag(&first_str, XML_ELEMENT_TPGT, Tag_End);
	tgt_buf_add_tag(&first_str, "create", Tag_End);

	node = tgt_door_call(first_str, 0);
	free(first_str);
	return (formatErrString(node));
}

/*ARGSUSED*/
static int
modifyTarget(int operandLen, char *operand[], cmdOptions_t *options)
{
	char		*first_str	= NULL;
	tgt_node_t	*node;
	cmdOptions_t	*optionList	= options;

	if (operand == NULL)
		return (1);

	tgt_buf_add_tag(&first_str, "modify", Tag_Start);
	tgt_buf_add_tag(&first_str, XML_ELEMENT_TARG, Tag_Start);
	tgt_buf_add(&first_str, XML_ELEMENT_NAME, operand[0]);

	for (; optionList->optval; optionList++) {
		switch (optionList->optval) {
			case 'p': /* tpgt number */
				tgt_buf_add(&first_str, XML_ELEMENT_TPGT,
				    optionList->optarg);
				break;
			case 'l': /* acl */
				tgt_buf_add(&first_str, XML_ELEMENT_ACL,
				    optionList->optarg);
				break;
			case 'a': /* alias */
				tgt_buf_add(&first_str, XML_ELEMENT_ALIAS,
				    optionList->optarg);
				break;
			case 'm': /* max recv */
				tgt_buf_add(&first_str, XML_ELEMENT_MAXRECV,
				    optionList->optarg);
				break;
			case 'z': /* grow lun size */
				tgt_buf_add(&first_str, XML_ELEMENT_SIZE,
				    optionList->optarg);
				break;
			case 'u':
				tgt_buf_add(&first_str, XML_ELEMENT_LUN,
				    optionList->optarg);
				break;
			default:
				(void) fprintf(stderr, "%s: %c: %s\n",
				    cmdName, optionList->optval,
				    gettext("unknown option"));
				free(first_str);
				return (1);
		}
	}

	tgt_buf_add_tag(&first_str, XML_ELEMENT_TARG, Tag_End);
	tgt_buf_add_tag(&first_str, "modify", Tag_End);

	node = tgt_door_call(first_str, 0);
	free(first_str);
	return (formatErrString(node));
}

/*ARGSUSED*/
static int
modifyInitiator(int operandLen, char *operand[], cmdOptions_t *options)
{
	char		*first_str	= NULL;
	tgt_node_t	*node;
	cmdOptions_t	*optionList	= options;
	char		chapSecret[MAX_CHAP_SECRET_LEN+1];
	int		secretLen	= 0;
	int		ret		= 0;

	if (operand == NULL)
		return (1);
	if (options == NULL)
		return (1);

	tgt_buf_add_tag(&first_str, "modify", Tag_Start);
	tgt_buf_add_tag(&first_str, XML_ELEMENT_INIT, Tag_Start);
	tgt_buf_add(&first_str, XML_ELEMENT_NAME, operand[0]);

	for (; optionList->optval; optionList++) {
		switch (optionList->optval) {
		case 'H': /* chap-name */
			if (strlen(optionList->optarg) != 0) {
				tgt_buf_add(&first_str, XML_ELEMENT_CHAPNAME,
				    optionList->optarg);
			} else {
				tgt_buf_add(&first_str,
				    XML_ELEMENT_DELETE_CHAPNAME,
				    OPT_TRUE);
			}
			break;
		case 'C': /* chap-secret */
			ret = getSecret((char *)&chapSecret[0], &secretLen,
			    MIN_CHAP_SECRET_LEN, MAX_CHAP_SECRET_LEN);
			if (ret != 0) {
				(void) fprintf(stderr, "%s: %s\n", cmdName,
				    gettext("Cannot read CHAP secret"));
				return (ret);
			}
			chapSecret[secretLen] = '\0';
			if (secretLen != 0) {
				tgt_buf_add(&first_str, XML_ELEMENT_CHAPSECRET,
				    chapSecret);
			} else {
				tgt_buf_add(&first_str,
				    XML_ELEMENT_DELETE_CHAPSECRET,
				    OPT_TRUE);
			}
			break;
		default:
			(void) fprintf(stderr, "%s: %c: %s\n",
			    cmdName, optionList->optval,
			    gettext("unknown option"));
			free(first_str);
			return (1);
		}
	}
	tgt_buf_add_tag(&first_str, XML_ELEMENT_INIT, Tag_End);
	tgt_buf_add_tag(&first_str, "modify", Tag_End);

	node = tgt_door_call(first_str, 0);
	free(first_str);
	return (formatErrString(node));
}

/*ARGSUSED*/
static int
modifyTpgt(int operandLen, char *operand[], cmdOptions_t *options)
{
	char		*first_str	= NULL;
	tgt_node_t	*node;
	cmdOptions_t	*optionList	= options;
	boolean_t	isIpv6 = B_FALSE;
	uint16_t	port;
	char		IpAddress[MAX_IPADDRESS_LEN];

	if (operand == NULL)
		return (1);
	if (optionList == NULL)
		return (1);

	tgt_buf_add_tag(&first_str, "modify", Tag_Start);
	tgt_buf_add_tag(&first_str, XML_ELEMENT_TPGT, Tag_Start);
	tgt_buf_add(&first_str, XML_ELEMENT_NAME, operand[0]);

	switch (optionList->optval) {
	case 'i': /* ip address */
		if (parseAddress(optionList->optarg, 0,
		    IpAddress, 256, &port, &isIpv6) !=
		    PARSE_ADDR_OK) {
			return (1);
		}
		tgt_buf_add(&first_str, XML_ELEMENT_IPADDR, IpAddress);
		break;
	default:
		(void) fprintf(stderr, "%s: %c: %s\n",
		    cmdName, optionList->optval,
		    gettext("unknown option"));
		free(first_str);
		return (1);
	}

	tgt_buf_add_tag(&first_str, XML_ELEMENT_TPGT, Tag_End);
	tgt_buf_add_tag(&first_str, "modify", Tag_End);

	node = tgt_door_call(first_str, 0);
	free(first_str);
	return (formatErrString(node));
}

/*ARGSUSED*/
static int
modifyAdmin(int operandLen, char *operand[], cmdOptions_t *options)
{
	char		*first_str	= NULL;
	tgt_node_t	*node;
	cmdOptions_t	*optionList	= options;
	char		chapSecret[MAX_CHAP_SECRET_LEN+1];
	char		olddir[MAXPATHLEN];
	char		newdir[MAXPATHLEN];
	int		secretLen	= 0;
	int		ret		= 0;

	if (operand == NULL)
		return (1);
	if (options == NULL)
		return (1);

	tgt_buf_add_tag(&first_str, "modify", Tag_Start);
	tgt_buf_add_tag(&first_str, XML_ELEMENT_ADMIN, Tag_Start);
	tgt_buf_add(&first_str, XML_ELEMENT_NAME, operand[0]);

	for (; optionList->optval; optionList++) {
		switch (optionList->optval) {
			case 'd': /* base directory */
				(void) getcwd(olddir, sizeof (olddir));

				/*
				 * Attempt to create the new base directory.
				 * This may fail for one of two reasons.
				 * (a) The path given is invalid or (b) it
				 * already exists. If (a) is true then then
				 * following chdir() will fail and the user
				 * notified. If (b) is true, then chdir() will
				 * succeed.
				 */
				(void) mkdir(optionList->optarg, 0700);

				if (chdir(optionList->optarg) == -1) {
					(void) fprintf(stderr, "%s: %s\n",
					    cmdName, gettext("Invalid path"));
					free(first_str);
					return (1);
				}
				(void) getcwd(newdir, sizeof (newdir));
				tgt_buf_add(&first_str, XML_ELEMENT_BASEDIR,
				    newdir);
				(void) chdir(olddir);
				break;
			case 'H': /* chap name */
				if (strlen(optionList->optarg) != 0) {
					tgt_buf_add(&first_str,
					    XML_ELEMENT_CHAPNAME,
					    optionList->optarg);
				} else {
					tgt_buf_add(&first_str,
					    XML_ELEMENT_DELETE_CHAPNAME,
					    OPT_TRUE);
				}
				break;
			case 'C': /* chap secert */
				ret = getSecret((char *)&chapSecret[0],
				    &secretLen,
				    MIN_CHAP_SECRET_LEN,
				    MAX_CHAP_SECRET_LEN);
				if (ret != 0) {
					(void) fprintf(stderr, "%s: %s\n",
					    cmdName,
					    gettext("Cannot read CHAP secret"));
					free(first_str);
					return (ret);
				}
				chapSecret[secretLen] = '\0';
				if (secretLen != 0) {
					tgt_buf_add(&first_str,
					    XML_ELEMENT_CHAPSECRET,
					    chapSecret);
				} else {
					tgt_buf_add(&first_str,
					    XML_ELEMENT_DELETE_CHAPSECRET,
					    OPT_TRUE);
				}
				break;
			case 'R': /* radius access */
				if (strcmp(optionList->optarg,
				    OPT_ENABLE) == 0) {
					tgt_buf_add(&first_str,
					    XML_ELEMENT_RAD_ACCESS, OPT_TRUE);
				} else
					if (strcmp(optionList->optarg,
					    OPT_DISABLE) == 0) {
					tgt_buf_add(&first_str,
					    XML_ELEMENT_RAD_ACCESS, OPT_FALSE);
				} else {
					(void) fprintf(stderr, "%s: %s\n",
					    cmdName,
					    gettext("Option value should be"
					    "enable/disable"));
					free(first_str);
					return (1);
				}
				break;
			case 'r': /* radius server */
				if (strlen(optionList->optarg) != 0) {
					tgt_buf_add(&first_str,
					    XML_ELEMENT_RAD_SERV,
					    optionList->optarg);
				} else {
					tgt_buf_add(&first_str,
					    XML_ELEMENT_DELETE_RAD_SERV,
					    OPT_TRUE);
				}
				break;
			case 'P': /* radius secret */
				ret = getSecret((char *)&chapSecret[0],
				    &secretLen, MIN_CHAP_SECRET_LEN,
				    MAX_CHAP_SECRET_LEN);
				if (ret != 0) {
					(void) fprintf(stderr, "%s: %s\n",
					    cmdName,
					    gettext("Cannot read RADIUS "
					    "secret"));
					free(first_str);
					return (ret);
				}
				chapSecret[secretLen] = '\0';
				if (secretLen != 0) {
					tgt_buf_add(&first_str,
					    XML_ELEMENT_RAD_SECRET,
					    chapSecret);
				} else {
					tgt_buf_add(&first_str,
					    XML_ELEMENT_DELETE_RAD_SECRET,
					    OPT_TRUE);
				}
				break;
			case 'S': /* iSNS access */
				if (strcmp(optionList->optarg,
				    OPT_ENABLE) == 0) {
					tgt_buf_add(&first_str,
					    XML_ELEMENT_ISNS_ACCESS, OPT_TRUE);
				} else
					if (strcmp(optionList->optarg,
					    OPT_DISABLE) == 0) {
					tgt_buf_add(&first_str,
					    XML_ELEMENT_ISNS_ACCESS, OPT_FALSE);
				} else {
					(void) fprintf(stderr, "%s: %s\n",
					    cmdName,
					    gettext("Option value should be"
					    "enable/disable"));
					free(first_str);
					return (1);
				}
				break;
			case 's': /* iSNS server */
				if (strlen(optionList->optarg) >
				    MAXHOSTNAMELEN) {
					(void) fprintf(stderr, "%s: %s\n",
					    cmdName,
					    gettext("option too long"));
					return (1);
				}
				tgt_buf_add(&first_str, XML_ELEMENT_ISNS_SERV,
				    optionList->optarg);
				break;
			case 'f': /* fast write back */
				if (strcmp(optionList->optarg,
				    OPT_ENABLE) == 0) {
					tgt_buf_add(&first_str,
					    XML_ELEMENT_FAST, OPT_TRUE);
				} else
					if (strcmp(optionList->optarg,
					    OPT_DISABLE) == 0) {
					tgt_buf_add(&first_str,
					    XML_ELEMENT_FAST, OPT_FALSE);
				} else {
					(void) fprintf(stderr, "%s: %s\n",
					    cmdName,
					    gettext("Option value should be"
					    "enable/disable"));
					free(first_str);
					return (1);
				}
				break;
			default:
				(void) fprintf(stderr, "%s: %c: %s\n",
				    cmdName, optionList->optval,
				    gettext("unknown option"));
				free(first_str);
				return (1);
		}
	}

	tgt_buf_add_tag(&first_str, XML_ELEMENT_ADMIN, Tag_End);
	tgt_buf_add_tag(&first_str, "modify", Tag_End);

	node = tgt_door_call(first_str, 0);
	free(first_str);
	return (formatErrString(node));
}

/*ARGSUSED*/
static int
deleteTarget(int operandLen, char *operand[], cmdOptions_t *options)
{
	char		*first_str	= NULL;
	tgt_node_t	*node;
	cmdOptions_t	*optionList = options;

	if (operand == NULL)
		return (1);
	if (options == NULL)
		return (1);

	tgt_buf_add_tag(&first_str, "delete", Tag_Start);
	tgt_buf_add_tag(&first_str, XML_ELEMENT_TARG, Tag_Start);
	tgt_buf_add(&first_str, XML_ELEMENT_NAME, operand[0]);

	switch (optionList->optval) {
	case 'u': /* all */
		tgt_buf_add(&first_str, XML_ELEMENT_LUN, optionList->optarg);
		break;
	case 'l': /* acl */
		tgt_buf_add(&first_str, XML_ELEMENT_ACL, optionList->optarg);
		break;
	case 'p': /* tpgt number */
		tgt_buf_add(&first_str, XML_ELEMENT_TPGT, optionList->optarg);
		break;
	default:
		(void) fprintf(stderr, "%s: %c: %s\n",
		    cmdName, optionList->optval,
		    gettext("unknown option"));
		free(first_str);
		return (1);
	}

	tgt_buf_add_tag(&first_str, XML_ELEMENT_TARG, Tag_End);
	tgt_buf_add_tag(&first_str, "delete", Tag_End);

	node = tgt_door_call(first_str, 0);
	free(first_str);
	return (formatErrString(node));
}

/*ARGSUSED*/
static int
deleteInitiator(int operandLen, char *operand[], cmdOptions_t *options)
{
	char		*first_str	= NULL;
	tgt_node_t	*node;
	cmdOptions_t	*optionList = options;

	if (operand == NULL)
		return (1);
	if (options == NULL)
		return (1);

	tgt_buf_add_tag(&first_str, "delete", Tag_Start);
	tgt_buf_add_tag(&first_str, XML_ELEMENT_INIT, Tag_Start);
	tgt_buf_add(&first_str, XML_ELEMENT_NAME, operand[0]);

	switch (optionList->optval) {
	case 'A': /* all */
		tgt_buf_add(&first_str, XML_ELEMENT_ALL, optionList->optarg);
		break;
	default:
		(void) fprintf(stderr, "%s: %c: %s\n",
		    cmdName, optionList->optval,
		    gettext("unknown option"));
		free(first_str);
		return (1);
	}

	tgt_buf_add_tag(&first_str, XML_ELEMENT_INIT, Tag_End);
	tgt_buf_add_tag(&first_str, "delete", Tag_End);

	node = tgt_door_call(first_str, 0);
	free(first_str);
	return (formatErrString(node));
}

/*ARGSUSED*/
static int
deleteTpgt(int operandLen, char *operand[], cmdOptions_t *options)
{
	char		*first_str	= NULL;
	tgt_node_t	*node;
	cmdOptions_t	*optionList = options;
	boolean_t	isIpv6 = B_FALSE;
	uint16_t	port;
	char		IpAddress[MAX_IPADDRESS_LEN];

	if (operand == NULL)
		return (1);
	if (options == NULL)
		return (1);

	tgt_buf_add_tag(&first_str, "delete", Tag_Start);
	tgt_buf_add_tag(&first_str, XML_ELEMENT_TPGT, Tag_Start);
	tgt_buf_add(&first_str, XML_ELEMENT_NAME, operand[0]);

	switch (optionList->optval) {
	case 'A': /* all */
		tgt_buf_add(&first_str, XML_ELEMENT_ALL, optionList->optarg);
		break;
	case 'i': /* ip address */
		if (parseAddress(optionList->optarg, 0,
		    IpAddress, 256, &port, &isIpv6) !=
		    PARSE_ADDR_OK) {
			return (1);
		}
		tgt_buf_add(&first_str, XML_ELEMENT_IPADDR, IpAddress);
		break;
	default:
		(void) fprintf(stderr, "%s: %c: %s\n",
		    cmdName, optionList->optval,
		    gettext("unknown option"));
		free(first_str);
		return (1);
	}

	tgt_buf_add_tag(&first_str, XML_ELEMENT_TPGT, Tag_End);
	tgt_buf_add_tag(&first_str, "delete", Tag_End);

	node = tgt_door_call(first_str, 0);
	free(first_str);
	return (formatErrString(node));
}

static int
listTarget(int operandLen, char *operand[], cmdOptions_t *options)
{
	char		*first_str	= NULL;
	tgt_node_t	*node		= NULL;
	tgt_node_t	*n1		= NULL; /* pointer to node (depth=1) */
	tgt_node_t	*n2		= NULL; /* pointer to node (depth=2) */
	tgt_node_t	*n3		= NULL; /* pointer to node (depth=3) */
	tgt_node_t	*n4		= NULL; /* pointer to node (depth=4) */
	int		conns;
	char		buf[32];
	Boolean_t	verbose		= False;

	if (operand == NULL)
		return (1);

	tgt_buf_add_tag(&first_str, "list", Tag_Start);
	tgt_buf_add_tag(&first_str, XML_ELEMENT_TARG, Tag_Start);

	if (operandLen)
		tgt_buf_add(&first_str, XML_ELEMENT_NAME, operand[0]);

	/*
	 * Always retrieve the iostats which will give us the
	 * connection count information even if we're not doing
	 * a verbose output.
	 */
	tgt_buf_add(&first_str, XML_ELEMENT_IOSTAT, OPT_TRUE);

	if (options) {
		switch (options->optval) {
		case 0:
			break;
		case 'v':
			tgt_buf_add(&first_str, XML_ELEMENT_LUNINFO, OPT_TRUE);
			verbose = True;
			break;
		default:
			(void) fprintf(stderr, "%s: %c: %s\n", cmdName,
			    options->optval, gettext("unknown option"));
			free(first_str);
			return (1);
		}
	}

	tgt_buf_add_tag(&first_str, XML_ELEMENT_TARG, Tag_End);
	tgt_buf_add_tag(&first_str, "list", Tag_End);

	if ((node = tgt_door_call(first_str, 0)) == NULL) {
		(void) fprintf(stderr, "%s: %s\n", cmdName,
		    gettext("No reponse from daemon"));
		return (1);
	}
	free(first_str);

	if (strcmp(node->x_name, XML_ELEMENT_RESULT)) {
		(void) fprintf(stderr, "%s: %s\n", cmdName,
		    gettext("Bad XML response"));
		return (1);
	}

	n1 = NULL;
	while ((n1 = tgt_node_next_child(node, XML_ELEMENT_TARG, n1)) != NULL) {
		(void) printf("%s: %s\n", gettext("Target"), n1->x_value);
		n2 = tgt_node_next_child(n1, XML_ELEMENT_INAME, NULL);
		(void) printf("%s%s: %s\n", dospace(1), gettext("iSCSI Name"),
		    n2 ? n2->x_value : gettext("Not set"));

		if ((n2 = tgt_node_next_child(n1, XML_ELEMENT_ALIAS, NULL)) !=
		    NULL)
			(void) printf("%s%s: %s\n", dospace(1),
			    gettext("Alias"), n2->x_value);

		if ((n2 = tgt_node_next_child(n1, XML_ELEMENT_MAXRECV, NULL)) !=
		    NULL)
			(void) printf("%s%s: %s\n", dospace(1),
			    gettext("MaxRecv"), n2->x_value);

		/*
		 * Count the number of connections available.
		 */
		n2 = NULL;
		conns = 0;
		while (n2 = tgt_node_next_child(n1, XML_ELEMENT_CONN, n2))
			conns++;
		(void) printf("%s%s: %d\n", dospace(1), gettext("Connections"),
		    conns);

		if (verbose == False)
			continue;

		/*
		 * Displaying the individual connections must be done
		 * first when verbose is turned on because you'll notice
		 * above that we've left the output hanging with a label
		 * indicating connections are coming next.
		 */
		n2 = NULL;
		while (n2 = tgt_node_next_child(n1, XML_ELEMENT_CONN, n2)) {
			(void) printf("%s%s:\n", dospace(2),
			    gettext("Initiator"));
			(void) printf("%s%s: %s\n", dospace(3),
			    gettext("iSCSI Name"), n2->x_value);
			n3 = tgt_node_next_child(n2, XML_ELEMENT_ALIAS, NULL);
			(void) printf("%s%s: %s\n", dospace(3),
			    gettext("Alias"),
			    n3 ? n3->x_value : gettext("unknown"));
		}

		(void) printf("%s%s:\n", dospace(1), gettext("ACL list"));
		n2 = tgt_node_next_child(n1, XML_ELEMENT_ACLLIST, NULL);
		n3 = NULL;
		while (n3 = tgt_node_next_child(n2, XML_ELEMENT_INIT, n3)) {
			(void) printf("%s%s: %s\n", dospace(2),
			    gettext("Initiator"),
			    n3->x_value);
		}

		(void) printf("%s%s:\n", dospace(1), gettext("TPGT list"));
		n2 = tgt_node_next_child(n1, XML_ELEMENT_TPGTLIST, NULL);
		n3 = NULL;
		while (n3 = tgt_node_next_child(n2, XML_ELEMENT_TPGT, n3)) {
			(void) printf("%s%s: %s\n", dospace(2),
			    gettext("TPGT"),
			    n3->x_value);
		}

		(void) printf("%s%s:\n", dospace(1),
		    gettext("LUN information"));
		n2 = tgt_node_next_child(n1, XML_ELEMENT_LUNINFO, NULL);
		n3 = NULL;
		while (n3 = tgt_node_next_child(n2, XML_ELEMENT_LUN, n3)) {
			(void) printf("%s%s: %s\n", dospace(2), gettext("LUN"),
			    n3->x_value);

			n4 = tgt_node_next_child(n3, XML_ELEMENT_GUID, NULL);
			(void) printf("%s%s: %s\n", dospace(3), gettext("GUID"),
			    n4 ? n4->x_value : gettext("unknown"));

			n4 = tgt_node_next_child(n3, XML_ELEMENT_VID, NULL);
			(void) printf("%s%s: %s\n", dospace(3), gettext("VID"),
			    n4 ? n4->x_value : gettext("unknown"));

			n4 = tgt_node_next_child(n3, XML_ELEMENT_PID, NULL);
			(void) printf("%s%s: %s\n", dospace(3), gettext("PID"),
			    n4 ? n4->x_value : gettext("unknown"));

			n4 = tgt_node_next_child(n3, XML_ELEMENT_DTYPE, NULL);
			(void) printf("%s%s: %s\n", dospace(3), gettext("Type"),
			    n4 ? n4->x_value : gettext("unknown"));

			n4 = tgt_node_next_child(n3, XML_ELEMENT_SIZE, NULL);
			if (n4 && (strtol(n4->x_value, NULL, 0) != 0)) {
				(void) printf("%s%s: %s\n", dospace(3),
				    gettext("Size"),
				    number_to_scaled_string(buf,
				    strtoll(n4->x_value,
				    NULL, 0), 512, 1024));
			} else {
				(void) printf("%s%s: %s\n", dospace(3),
				    gettext("Size"), gettext("unknown"));
			}

			n4 = tgt_node_next_child(n3, XML_ELEMENT_BACK, NULL);
			if (n4) {
				(void) printf("%s%s: %s\n", dospace(3),
				    gettext("Backing store"), n4->x_value);
			}

			n4 = tgt_node_next_child(n3, XML_ELEMENT_STATUS, NULL);
			(void) printf("%s%s: %s\n", dospace(3),
			    gettext("Status"),
			    n4 ? n4->x_value : gettext("unknown"));
		}
	}

	return (0);
}

static int
listInitiator(int operandLen, char *operand[], cmdOptions_t *options)
{
	char		*first_str	= NULL;
	tgt_node_t	*node;
	tgt_node_t	*n1		= NULL; /* pointer to node (depth=1) */
	tgt_node_t	*n2		= NULL; /* pointer to node (depth=2) */
	Boolean_t	verbose		= False;
	cmdOptions_t	*optionList	= options;

	if (operand == NULL)
		return (1);

	tgt_buf_add_tag(&first_str, "list", Tag_Start);
	tgt_buf_add_tag(&first_str, XML_ELEMENT_INIT, Tag_Start);

	if (operandLen) {
		tgt_buf_add(&first_str, XML_ELEMENT_NAME, operand[0]);
	}
	if (optionList) {
		switch (optionList->optval) {
		case 0:
			break;
		case 'v':
			verbose = True;
			tgt_buf_add(&first_str,
			    XML_ELEMENT_VERBOSE, OPT_TRUE);
			break;

		default:
			(void) fprintf(stderr, "%s: %c: %s\n",
			    cmdName, optionList->optval,
			    gettext("unknown option"));
			free(first_str);
			return (1);
		}
	}

	tgt_buf_add_tag(&first_str, XML_ELEMENT_INIT, Tag_End);
	tgt_buf_add_tag(&first_str, "list", Tag_End);

	if ((node = tgt_door_call(first_str, 0)) == NULL) {
		(void) fprintf(stderr, "%s: %s\n", cmdName,
		    gettext("No reponse from daemon"));
		return (1);
	}
	free(first_str);

	if (strcmp(node->x_name, XML_ELEMENT_RESULT)) {
		(void) fprintf(stderr, "%s: %s\n", cmdName,
		    gettext("Bad XML response"));
		return (1);
	}

	n1 = NULL;
	while (n1 = tgt_node_next_child(node, XML_ELEMENT_INIT, n1)) {
		(void) printf("%s: %s\n", gettext("Initiator"), n1->x_value);

		n2 = tgt_node_next_child(n1, XML_ELEMENT_INAME, NULL);
		(void) printf("%s%s: %s\n", dospace(1), gettext("iSCSI Name"),
		    n2 ? n2->x_value : gettext("Not set"));

		n2 = tgt_node_next_child(n1, XML_ELEMENT_CHAPNAME, NULL);
		(void) printf("%s%s: %s\n", dospace(1), gettext("CHAP Name"),
		    n2 ? n2->x_value : gettext("Not set"));

		if (verbose == True) {
			n2 = tgt_node_next_child(n1, XML_ELEMENT_CHAPSECRET,
			    NULL);
			(void) printf("%s%s: %s\n", dospace(1),
			    gettext("CHAP Secret"),
			    n2 ? gettext("Set") : gettext("Not set"));
		}

	}

	return (0);
}

static int
listTpgt(int operandLen, char *operand[], cmdOptions_t *options)
{
	char		*first_str	= NULL;
	tgt_node_t	*node		= NULL;
	tgt_node_t	*n1		= NULL; /* pointer to node (depth=1) */
	tgt_node_t	*n2		= NULL; /* pointer to node (depth=2) */
	cmdOptions_t	*optionList	= options;
	Boolean_t	verbose		= False;
	int		addrs;

	if (operand == NULL)
		return (1);

	tgt_buf_add_tag(&first_str, "list", Tag_Start);
	tgt_buf_add_tag(&first_str, XML_ELEMENT_TPGT, Tag_Start);

	if (operandLen)
		tgt_buf_add(&first_str, XML_ELEMENT_NAME, operand[0]);
	if (optionList) {
		switch (optionList->optval) {
		case 0: /* no options, treat as --verbose */
			break;
		case 'v':
			verbose = True;
			tgt_buf_add(&first_str,
			    XML_ELEMENT_VERBOSE, OPT_TRUE);
			break;
		default:
			(void) fprintf(stderr, "%s: %c: %s\n",
			    cmdName, optionList->optval,
			    gettext("unknown option"));
			free(first_str);
			return (1);
		}
	}

	tgt_buf_add_tag(&first_str, XML_ELEMENT_TPGT, Tag_End);
	tgt_buf_add_tag(&first_str, "list", Tag_End);

	if ((node = tgt_door_call(first_str, 0)) == NULL) {
		(void) fprintf(stderr, "%s: %s\n", cmdName,
		    gettext("No reponse from daemon"));
		return (1);
	}
	free(first_str);

	if (strcmp(node->x_name, XML_ELEMENT_RESULT)) {
		(void) fprintf(stderr, "%s: %s\n", cmdName,
		    gettext("Bad XML response"));
		return (1);
	}

	n1 = NULL;
	while (n1 = tgt_node_next_child(node, XML_ELEMENT_TPGT, n1)) {
		(void) printf("%s: %s\n", gettext("TPGT"), n1->x_value);
		n2 = NULL;
		addrs = 0;
		while (n2 = tgt_node_next(n1, XML_ELEMENT_IPADDR, n2)) {
			if (verbose == True)
				(void) printf("%s%s: %s\n", dospace(1),
				    gettext("IP Address"),
				    n2 ? n2->x_value : gettext("Not set"));
			addrs++;
		}

		if (verbose == False) {
			(void) printf("%s%s: %d\n", dospace(1),
			    gettext("IP Address count"), addrs);
		} else if (addrs == 0) {

			/*
			 * Verbose is true, but there where no addresses
			 * for this TPGT. To keep the output consistent
			 * dump a "Not set" string out.
			 */
			(void) printf("%s%s: %s\n", dospace(1),
			    gettext("IP Address"), gettext("Not set"));
		}
	}

	return (0);
}

/*ARGSUSED*/
static int
showAdmin(int operandLen, char *operand[], cmdOptions_t *options)
{
	char		*first_str	= NULL;
	tgt_node_t	*node		= NULL;
	tgt_node_t	*n1		= NULL; /* pointer to node (depth=1) */
	tgt_node_t	*n2		= NULL; /* pointer to node (depth=2) */

	if (operand == NULL)
		return (1);

	tgt_buf_add_tag(&first_str, "list", Tag_Start);
	tgt_buf_add_tag(&first_str, XML_ELEMENT_ADMIN, Tag_Start);
	tgt_buf_add_tag(&first_str, XML_ELEMENT_ADMIN, Tag_End);
	tgt_buf_add_tag(&first_str, "list", Tag_End);

	if ((node = tgt_door_call(first_str, 0)) == NULL) {
		(void) fprintf(stderr, "%s: %s\n", cmdName,
		    gettext("No reponse from daemon"));
		return (1);
	}
	free(first_str);

	if (strcmp(node->x_name, XML_ELEMENT_RESULT)) {
		(void) fprintf(stderr, "%s: %s\n", cmdName,
		    gettext("Bad XML response"));
		return (1);
	}

	(void) printf("%s:\n", cmdName);

	n1 = tgt_node_next_child(node, XML_ELEMENT_ADMIN, NULL);
	if (n1 == NULL) {
		(void) fprintf(stderr, "%s: %s\n", cmdName,
		    gettext("Bad XML response"));
		return (1);
	}

	n2 = tgt_node_next_child(n1, XML_ELEMENT_BASEDIR, NULL);
	(void) printf("%s%s: %s\n", dospace(1), gettext("Base Directory"),
	    n2 ? n2->x_value : gettext("Not set"));

	n2 = tgt_node_next_child(n1, XML_ELEMENT_CHAPNAME, NULL);
	(void) printf("%s%s: %s\n", dospace(1), gettext("CHAP Name"),
	    n2 ? n2->x_value : gettext("Not set"));

	n2 = tgt_node_next_child(n1, XML_ELEMENT_RAD_ACCESS, NULL);
	(void) printf("%s%s: ", dospace(1), gettext("RADIUS Access"));
	if (n2) {
		if (strcmp(n2->x_value, OPT_TRUE) == 0)
			(void) printf("%s\n", gettext("Enabled"));
		else
			(void) printf("%s\n", gettext("Disabled"));
	} else
		(void) printf("%s\n", gettext("Not set"));

	n2 = tgt_node_next_child(n1, XML_ELEMENT_RAD_SERV, NULL);
	(void) printf("%s%s: %s\n", dospace(1), gettext("RADIUS Server"),
	    n2 ? n2->x_value : gettext("Not set"));

	n2 = tgt_node_next_child(n1, XML_ELEMENT_ISNS_ACCESS, NULL);
	(void) printf("%s%s: ", dospace(1), gettext("iSNS Access"));
	if (n2) {
		if (strcmp(n2->x_value, OPT_TRUE) == 0)
			(void) printf("%s\n", gettext("Enabled"));
		else
			(void) printf("%s\n", gettext("Disabled"));
	} else
		(void) printf("%s\n", gettext("Not set"));

	n2 = tgt_node_next_child(n1, XML_ELEMENT_ISNS_SERV, NULL);
	(void) printf("%s%s: %s\n", dospace(1), gettext("iSNS Server"),
	    n2 ? n2->x_value : gettext("Not set"));

	n2 = tgt_node_next_child(n1, XML_ELEMENT_ISNS_SERVER_STATUS, NULL);
	if (n2) {
		/*
		 * if NULL, that means either the isns discovery is
		 * disabled or the server address is not set.
		 */
		if (n2->x_value != NULL) {
			(void) printf("%s%s: ", dospace(1),
			    gettext("iSNS Server Status"));
			(void) printf("%s\n", n2->x_value);
		}
	}

	n2 = tgt_node_next_child(n1, XML_ELEMENT_FAST, NULL);
	(void) printf("%s%s: ", dospace(1), gettext("Fast Write ACK"));
	if (n2) {
		if (strcmp(n2->x_value, OPT_TRUE) == 0)
			(void) printf("%s\n", gettext("Enabled"));
		else
			(void) printf("%s\n", gettext("Disabled"));
	} else
		(void) printf("%s\n", gettext("Not set"));

	return (0);
}

static int
showStats(int operandLen, char *operand[], cmdOptions_t *options)
{
	char		*first_str	= NULL;
	char		scale_buf[16];
	tgt_node_t	*node, *n1;
	int		interval	= -1;
	int		count		= -1;
	int		header;
	stat_delta_t	cur_data, *pd;

	tgt_buf_add_tag(&first_str, "list", Tag_Start);
	tgt_buf_add_tag(&first_str, XML_ELEMENT_TARG, Tag_Start);

	tgt_buf_add(&first_str, XML_ELEMENT_IOSTAT, OPT_TRUE);
	if (operandLen)
		tgt_buf_add(&first_str, XML_ELEMENT_NAME, operand[0]);

	for (; options->optval; options++) {
		switch (options->optval) {
		case 0:
			break;
		case 'I': /* optarg = refresh interval */
			interval = atoi(options->optarg);
			if (interval == 0) {
				(void) fprintf(stderr, "%s: %s\n", cmdName,
				    gettext("interval must be non-zero"));
				free(first_str);
				return (1);
			}
			break;
		case 'N':
			count = atoi(options->optarg);
			if (count == 0) {
				(void) fprintf(stderr, "%s: %s\n", cmdName,
				    gettext("count must be non-zero"));
				free(first_str);
				return (1);
			}
			break;
		default:
			(void) fprintf(stderr, "%s: %c: %s\n", cmdName,
			    options->optval, gettext("unknown option"));
			free(first_str);
			return (1);
		}
	}

	tgt_buf_add_tag(&first_str, XML_ELEMENT_TARG, Tag_End);
	tgt_buf_add_tag(&first_str, "list", Tag_End);

	header = 1;
	/*CONSTANTCONDITION*/
	while (1) {
		if (--header == 0) {
			(void) printf("%20s  %12s  %12s\n", " ",
			    gettext("operations"), gettext("bandwidth "));
			(void) printf("%-20s  %5s  %5s  %5s  %5s\n",
			    gettext("device"), gettext("read"),
			    gettext("write"), gettext("read"),
			    gettext("write"));
			(void) printf("%-20s  %5s  %5s  %5s  %5s\n",
			    "--------------------", "-----", "-----",
			    "-----", "-----");
			header = 20;
		}
		if ((node = tgt_door_call(first_str, 0)) == NULL) {
			(void) fprintf(stderr, "%s: %s\n", cmdName,
			    gettext("No reponse from daemon"));
			return (1);
		}

		if (strcmp(node->x_name, XML_ELEMENT_RESULT)) {
			(void) fprintf(stderr, "%s: %s\n", cmdName,
			    gettext("Bad XML response"));
			free(first_str);
			tgt_node_free(node);
			stats_free();
			return (1);
		}

		n1 = NULL;
		while (n1 = tgt_node_next_child(node, XML_ELEMENT_TARG, n1)) {
			stats_load_counts(n1, &cur_data);
			if ((pd = stats_prev_counts(&cur_data)) == NULL) {
				free(first_str);
				tgt_node_free(node);
				return (1);
			}
			(void) printf("%-20s  ", pd->device);
			(void) printf("%5s  ",
			    number_to_scaled_string(scale_buf,
			    cur_data.read_cmds - pd->read_cmds, 1, 1024));
			(void) printf("%5s  ",
			    number_to_scaled_string(scale_buf,
			    cur_data.write_cmds - pd->write_cmds, 1, 1024));
			(void) printf("%5s  ",
			    number_to_scaled_string(scale_buf,
			    cur_data.read_blks - pd->read_blks, 512, 1024));
			(void) printf("%5s\n",
			    number_to_scaled_string(scale_buf,
			    cur_data.write_blks - pd->write_blks, 512, 1024));
			stats_update_counts(pd, &cur_data);
		}
		tgt_node_free(node);

		if (count == -1) {
			if (interval == -1)
				/* No count or internal, do it just once */
				break;
			else
				(void) sleep(interval);
		} else if (--count) {
			if (interval == -1)
				break;
			else
				(void) sleep(interval);
		} else
			break;
	}

	stats_free();
	free(first_str);
	return (0);
}

/*
 * input:
 *  execFullName - exec name of program (argv[0])
 *
 * Returns:
 *  command name portion of execFullName
 */
static char *
getExecBasename(char *execFullname)
{
	char *lastSlash, *execBasename;

	/* guard against '/' at end of command invocation */
	for (;;) {
		lastSlash = strrchr(execFullname, '/');
		if (lastSlash == NULL) {
			execBasename = execFullname;
			break;
		} else {
			execBasename = lastSlash + 1;
			if (*execBasename == '\0') {
				*lastSlash = '\0';
				continue;
			}
			break;
		}
	}
	return (execBasename);
}

/*
 * main calls a parser that checks syntax of the input command against
 * various rules tables.
 *
 * The parser provides usage feedback based upon same tables by calling
 * two usage functions, usage and subUsage, handling command and subcommand
 * usage respectively.
 *
 * The parser handles all printing of usage syntactical errors
 *
 * When syntax is successfully validated, the parser calls the associated
 * function using the subcommands table functions.
 *
 * Syntax is as follows:
 *	command subcommand [options] resource-type [<object>]
 *
 * The return value from the function is placed in funcRet
 */
int
main(int argc, char *argv[])
{
	synTables_t synTables;
	char versionString[VERSION_STRING_MAX_LEN];
	int ret;
	int funcRet;
	void *subcommandArgs = NULL;

	/* set global command name */
	cmdName = getExecBasename(argv[0]);

	if (getzoneid() != GLOBAL_ZONEID) {
		(void) fprintf(stderr,
		    "%s: this command is only available in the 'global' "
		    "zone\n", cmdName);
		exit(1);
	}

	(void) snprintf(versionString, sizeof (versionString), "%s.%s",
	    VERSION_STRING_MAJOR, VERSION_STRING_MINOR);
	synTables.versionString = versionString;
	synTables.longOptionTbl = &longOptions[0];
	synTables.subcommandTbl = &subcommands[0];
	synTables.objectTbl = &objects[0];
	synTables.objectRulesTbl = &objectRules[0];
	synTables.optionRulesTbl = &optionRules[0];

	/* call the CLI parser */
	ret = cmdParse(argc, argv, synTables, subcommandArgs, &funcRet);
	if (ret == 1) {
		(void) printf("%s %s(1M)\n",
		    gettext("For more information, please see"), cmdName);
		return (1);
	} else if (ret == -1) {
		perror(cmdName);
		return (1);
	}

	return (funcRet);
}