/*
 * 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 (c) 2010, Oracle and/or its affiliates. All rights reserved.
 */
#include <stdlib.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <getopt.h>
#include <strings.h>
#include <ctype.h>
#include <libnvpair.h>
#include <libintl.h>
#include <libgen.h>
#include <pwd.h>
#include <auth_attr.h>
#include <secdb.h>
#include <libscf.h>
#include <limits.h>
#include <locale.h>
#include <dirent.h>

#include <libstmf.h>
#include <libsrpt.h>

/* SMF service info */
#define	STMF_SVC	"svc:/system/stmf:default"

#define	STMF_STALE(ret) {\
	if (ret == STMF_ERROR_PROV_DATA_STALE) {\
		(void) fprintf(stderr, "%s\n",\
		    gettext("Configuration changed during processing.  "\
		    "Check the configuration, then retry this command "\
		    "if appropriate."));\
	}\
}

#define	SRPTADM_CHKAUTH(sec) {\
	if (!chkauthattr(sec, srptadm_uname)) {\
		(void) fprintf(stderr,\
		    gettext("Error, operation requires authorization %s"),\
		    sec);\
		(void) fprintf(stderr, "\n");\
		return (1);\
	}\
}

#define	PROPS_FORMAT	"    %-20s: "

static struct option srptadm_long[] = {
	{"enable",		no_argument,		NULL, 'e'},
	{"disable",		no_argument,		NULL, 'd'},
	{"reset",		no_argument,		NULL, 'r'},
	{"help",		no_argument,		NULL, '?'},
	{"help",		no_argument,		NULL, 'h'},
	{NULL, 0, NULL, 0}
};

static char m_def[] = "srptadm modify-defaults [-e] [-d]";
static char l_def[] = "srptadm list-defaults";
static char s_tgt[] = "srptadm modify-target [-e] [-d] [-r] <hca>";
static char l_tgt[] = "srptadm list-target [<hca>]";

/* keep the order of this enum in the same order as the 'subcmds' struct */
typedef enum {
	MODIFY_DEFAULT,
	LIST_DEFAULT,
	MODIFY_TARGET,
	LIST_TARGET,
	NULL_SUBCMD	/* must always be last! */
} srptadm_sub_t;

typedef struct {
	char		*name;
	char		*shortopts;
	char		*usemsg;
} srptadm_subcmds_t;

static srptadm_subcmds_t	subcmds[] = {
	{"modify-defaults", "edh?", m_def},
	{"list-defaults", "h?", l_def},
	{"modify-target", "edrh?", s_tgt},
	{"list-target", "h?", l_tgt},
	{NULL, ":h?", NULL},
};

/* used for checking if user is authorized */
static char *srptadm_uname = NULL;

/* prototypes */
static int get_local_hcas(char **hcaArray, int count);
static int print_target_props(char *hca);
static int list_target(char *hca);
static int disable_target(char *hca);
static int reset_target(char *hca);
static int list_defaults(void);
static int enable_target(char *hca);
static int set_default_state(boolean_t enabled);

int
main(int argc, char *argv[])
{
	int		ret = 0;
	int		idx = NULL_SUBCMD;
	char		c;
	int		newargc = argc;
	char		**newargv = NULL;
	char		*objp;
	int		srptind = 0;
	struct passwd	*pwd = NULL;
	char		*smfstate = NULL;
	boolean_t	reset = B_FALSE;
	int		dflag = 0;
	int		eflag = 0;

	(void) setlocale(LC_ALL, "");
	(void) textdomain(TEXT_DOMAIN);

	if (argc < 2) {
		ret = 1;
		goto usage_error;
	}

	for (idx = 0; subcmds[idx].name != NULL; idx++) {
		if (strcmp(argv[1], subcmds[idx].name) == 0) {
			break;
		}
	}

	/* get the caller's user name for subsequent chkauthattr() calls */
	pwd = getpwuid(getuid());
	if (pwd == NULL) {
		(void) fprintf(stderr, "%s\n",
		    gettext("Could not determine callers user name."));
		return (1);
	}

	srptadm_uname = strdup(pwd->pw_name);

	/* increment past command & subcommand */
	newargc--;
	newargv = &(argv[1]);

	while ((ret == 0) && (newargv)) {
		c = getopt_long(newargc, newargv, subcmds[idx].shortopts,
		    srptadm_long, &srptind);
		if (c == -1) {
			break;
		}

		switch (c) {
			case 0:
				/* flag set by getopt */
				break;
			case 'd':
				dflag++;
				break;
			case 'e':
				eflag++;
				break;
			case 'r':
				reset = B_TRUE;
				break;
			case '?':
				/*
				 * '?' is returned for both unrecognized
				 * options and if explicitly provided on
				 * the command line.  The latter should
				 * be handled the same as -h.
				 */
				if (strcmp(newargv[optind-1], "-?") != 0) {
					(void) fprintf(stderr,
					    gettext("Unrecognized option %s"),
					    newargv[optind-1]);
					(void) fprintf(stderr, "\n");
					ret = 1;
				}
				goto usage_error;
			case 'h':
				goto usage_error;
			case ':':
				(void) fprintf(stderr,
				    gettext("Option %s requires an operand."),
				    newargv[optind-1]);
				(void) fprintf(stderr, "\n");

				/* fall through to default */
			default:
				ret = 1;
				break;
		}
	}

	if (ret != 0) {
		goto usage_error;
	}

	/* after getopt() to allow handling of -h option */
	if ((srptadm_sub_t)idx == NULL_SUBCMD) {
		(void) fprintf(stderr, "%s\n",
		    gettext("Error, no subcommand specified"));
		ret = 1;
		goto usage_error;
	}

	newargc -= optind;
	if (newargc == 0) {
		newargv = NULL;
		objp = NULL;
	} else {
		newargv = &(newargv[optind]);
		objp = newargv[0];
	}

	if (objp == NULL) {
		switch ((srptadm_sub_t)idx) {
		case MODIFY_TARGET:
			/* These subcommands need operands */
			ret = 1;
			goto usage_error;
		default:
			break;
		}
	}

	if (newargc > 1) {
		switch ((srptadm_sub_t)idx) {
		case MODIFY_TARGET:
		case LIST_TARGET:
			/* These subcommands should have at most one operand */
			ret = 1;
			goto usage_error;

		default:
			break;
		}
	}


	/*
	 * Make sure STMF service is enabled before proceeding.
	 */
	smfstate = smf_get_state(STMF_SVC);
	if (!smfstate ||
	    (strcmp(smfstate, SCF_STATE_STRING_ONLINE) != 0)) {
		(void) fprintf(stderr, "%s\n",
		    gettext("The STMF service must be online "
		    "before running this command."));
		(void) fprintf(stderr,
		    gettext("Use 'svcadm enable -r %s'"), STMF_SVC);
		(void) fprintf(stderr, "\n");
		(void) fprintf(stderr, "%s\n",
		    gettext("to enable the service and its prerequisite "
		    "services and/or"));
		(void) fprintf(stderr,
		    gettext("'svcs -x %s' to determine why it is not online."),
		    STMF_SVC);
		(void) fprintf(stderr, "\n");

		return (1);
	}

	switch ((srptadm_sub_t)idx) {
		case MODIFY_DEFAULT:
			if (eflag) {
				ret = set_default_state(B_TRUE);
			} else if (dflag) {
				ret = set_default_state(B_FALSE);
			} else {
				ret = 1;
				goto usage_error;
			}
			break;
		case LIST_DEFAULT:
			ret = list_defaults();
			break;
		case MODIFY_TARGET:
			if (reset) {
				ret = reset_target(objp);
			} else if (eflag) {
				ret = enable_target(objp);
			} else if (dflag) {
				ret = disable_target(objp);
			} else {
				ret = 1;
				goto usage_error;
			}
			break;
		case LIST_TARGET:
			ret = list_target(objp);
			break;
		default:
			ret = 1;
			goto usage_error;
	}

	if (ret != 0) {
		(void) fprintf(stderr,
		    gettext("srptadm %s failed with error %d"),
		    subcmds[idx].name, ret);
		(void) fprintf(stderr, "\n");
	}
	return (ret);

usage_error:
	if (subcmds[idx].name) {
		(void) printf("%s\n", gettext(subcmds[idx].usemsg));
	} else {
		/* overall usage */
		(void) printf("%s\n\n", gettext("srptadm usage:"));
		for (idx = 0; subcmds[idx].name != NULL; idx++) {
			if (!subcmds[idx].usemsg) {
				continue;
			}
			(void) printf("\t%s\n", gettext(subcmds[idx].usemsg));
		}
	}

	return (ret);
}

static int
set_default_state(boolean_t enabled)
{
	int		ret;
	char		*sec = "solaris.smf.modify.stmf";

	SRPTADM_CHKAUTH(sec);

	ret = srpt_SetDefaultState(enabled);

	return (ret);
}

static int
enable_target(char *hca)
{
	int		ret;
	char		*sec = "solaris.smf.modify.stmf";

	SRPTADM_CHKAUTH(sec);

	ret = srpt_SetTargetState(hca, B_TRUE);

	return (ret);
}

static int
disable_target(char *hca)
{
	int		ret;
	char		*sec = "solaris.smf.modify.stmf";

	SRPTADM_CHKAUTH(sec);

	ret = srpt_SetTargetState(hca, B_FALSE);

	return (ret);
}

static int
reset_target(char *hca)
{
	int		ret;
	char		*sec = "solaris.smf.modify.stmf";

	SRPTADM_CHKAUTH(sec);

	ret = srpt_ResetTarget(hca);

	return (ret);
}

static int
list_defaults(void)
{
	int		ret;
	char		*sec = "solaris.smf.read.stmf";
	boolean_t	enabled;

	SRPTADM_CHKAUTH(sec);

	/* only state set as default for now */
	ret = srpt_GetDefaultState(&enabled);

	if (ret == 0) {
		(void) printf("%s:\n\n",
		    gettext("SRP Target Service Default Properties"));

		(void) printf("    %s:\t",
		    gettext("Target creation enabled by default"));

		if (enabled) {
			(void) printf("%s\n", gettext("true"));
		} else {
			(void) printf("%s\n", gettext("false"));
		}
	}

	return (ret);
}

static int
list_target(char *hca)
{
	int		ret;
	char		*sec = "solaris.smf.read.stmf";
	char		*hcaArr[1024];	/* way bigger than we'll ever see */
	int		i;

	SRPTADM_CHKAUTH(sec);

	if (hca != NULL) {
		ret = print_target_props(hca);
		return (ret);
	}

	/* get list of HCAs configured on this system, from /dev/cfg */
	(void) memset(&hcaArr, 0, 1024 * sizeof (char *));

	ret = get_local_hcas(hcaArr, sizeof (hcaArr));
	if (ret == ETOOMANYREFS) {
		(void) fprintf(stderr, "Internal error:  too many HCAs\n");
		goto done;
	} else if (ret != 0) {
		(void) fprintf(stderr, "Error getting list of HCAs: %d\n", ret);
		goto done;
	}

	for (i = 0; i < 1024; i++) {
		if (hcaArr[i] == NULL) {
			break;
		}
		ret = print_target_props(hcaArr[i]);
	}

done:
	for (i = 0; i < 1024; i++) {
		if (hcaArr[i] == NULL) {
			break;
		}
		free(hcaArr[i]);
	}

	return (ret);
}

static int
print_target_props(char *hca)
{
	int		ret;
	boolean_t	enabled;
	char		buf[32];
	char		euibuf[64];
	uint64_t	hcaguid;
	stmfDevid	devid;
	stmfTargetProperties	props;
	char		*state;

	ret = srpt_NormalizeGuid(hca, buf, sizeof (buf), &hcaguid);
	if (ret != 0) {
		(void) fprintf(stderr, "Invalid target HCA: %s\n",
		    hca);
		return (ret);
	}

	/* only property set is enabled */
	ret = srpt_GetTargetState(buf, &enabled);
	if (ret != 0) {
		(void) fprintf(stderr,
		    "Could not get enabled state for %s: %d\n",
		    buf, ret);
		return (ret);
	}

	(void) printf("Target HCA %s:\n", buf);

	(void) printf(PROPS_FORMAT, gettext("Enabled"));

	if (enabled) {
		(void) printf("%s\n", gettext("true"));
	} else {
		(void) printf("%s\n", gettext("false"));
	}

	state = "-";

	(void) snprintf(euibuf, sizeof (euibuf), "eui.%016llX", hcaguid);

	ret = stmfDevidFromIscsiName(euibuf, &devid);
	if (ret == STMF_STATUS_SUCCESS) {
		ret = stmfGetTargetProperties(&devid, &props);
		if (ret == STMF_STATUS_SUCCESS) {
			if (props.status == STMF_TARGET_PORT_ONLINE) {
				state = "online";
			} else {
				state = "offline";
			}
		}
	}

	(void) printf(PROPS_FORMAT, gettext("SRP Target Name"));
	(void) printf("%s\n", euibuf);
	(void) printf(PROPS_FORMAT, gettext("Operational Status"));
	(void) printf("%s\n", state);

	(void) printf("\n");

	return (0);
}


static int
get_local_hcas(char **hcaArray, int count)
{
	int		ret = 0;
	char		*cfgdir = "/dev/cfg";
	DIR		*dirp = NULL;
	struct dirent	*entry;
	int		idx = 0;
	char		*bufp;

	if ((hcaArray == NULL) || (count == 0)) {
		return (EINVAL);
	}

	dirp = opendir(cfgdir);

	if (dirp == NULL) {
		ret = errno;
		(void) fprintf(stderr, "Could not open %s: errno %d\n",
		    cfgdir, ret);
		return (ret);
	}

	while ((entry = readdir(dirp)) != NULL) {
		bufp = &entry->d_name[0];

		if (strncmp(bufp, "hca:", 4) != 0) {
			continue;
		}

		bufp += 4;

		hcaArray[idx] = strdup(bufp);
		if (hcaArray[idx] == NULL) {
			ret = ENOMEM;
			break;
		}
		idx++;

		if (idx >= count) {
			ret = ETOOMANYREFS;
			break;
		}
	}

	return (ret);
}