/*
 * Helper functions to represent iSNS objects as text,
 * and/or to parse objects represented in textual form.
 * These functions can be used by command line utilities
 * such as isnsadm, as well as applications like iscsid
 * or stgtd when talking to the iSNS discovery daemon.
 *
 * Copyright (C) 2007 Olaf Kirch <olaf.kirch@oracle.com>
 */

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <ctype.h>

#include "config.h"
#include <libisns/isns.h>
#include <libisns/util.h>
#include "vendor.h"
#include <libisns/attrs.h>
#include "security.h"
#include "objects.h"
#include <libisns/paths.h>

#define MAX_ALIASES		4

struct isns_tag_prefix {
	const char *		name;
	unsigned int		name_len;
	isns_object_template_t *context;
};

struct tag_name {
	const char *		name;
	uint32_t		tag;
	struct isns_tag_prefix *prefix;
	const char *		alias[MAX_ALIASES];
};

static struct isns_tag_prefix all_prefixes[__ISNS_OBJECT_TYPE_MAX] = {
[ISNS_OBJECT_TYPE_ENTITY] = { "entity-", 7, &isns_entity_template	},
[ISNS_OBJECT_TYPE_NODE]   = { "iscsi-",	 6, &isns_iscsi_node_template	},
[ISNS_OBJECT_TYPE_PORTAL] = { "portal-", 7, &isns_portal_template	},
[ISNS_OBJECT_TYPE_PG]     = { "pg-",	 3, &isns_iscsi_pg_template	},
[ISNS_OBJECT_TYPE_DD]     = { "dd-",	 3, &isns_dd_template		},
[ISNS_OBJECT_TYPE_POLICY] = { "policy-", 7, &isns_policy_template	},
};

static struct tag_name	all_attrs[] = {
{ .name = "id", .tag = ISNS_TAG_ENTITY_IDENTIFIER, .alias = { "eid", }, },
{ .name = "prot",		.tag = ISNS_TAG_ENTITY_PROTOCOL },
{ .name = "idx",		.tag = ISNS_TAG_ENTITY_INDEX },

{ .name = "name",		.tag = ISNS_TAG_ISCSI_NAME },
{ .name = "node-type",		.tag = ISNS_TAG_ISCSI_NODE_TYPE },
{ .name = "alias",		.tag = ISNS_TAG_ISCSI_ALIAS },
{ .name = "authmethod",		.tag = ISNS_TAG_ISCSI_AUTHMETHOD },
{ .name = "idx",		.tag = ISNS_TAG_ISCSI_NODE_INDEX },

{ .name = "addr",		.tag = ISNS_TAG_PORTAL_IP_ADDRESS },
{ .name = "port",		.tag = ISNS_TAG_PORTAL_TCP_UDP_PORT },
{ .name = "name",		.tag = ISNS_TAG_PORTAL_SYMBOLIC_NAME },
{ .name = "esi-port",		.tag = ISNS_TAG_ESI_PORT },
{ .name = "esi-interval",	.tag = ISNS_TAG_ESI_INTERVAL },
{ .name = "scn-port",		.tag = ISNS_TAG_SCN_PORT },
{ .name = "idx",		.tag = ISNS_TAG_PORTAL_INDEX },

{ .name = "name",		.tag = ISNS_TAG_PG_ISCSI_NAME },
{ .name = "addr",		.tag = ISNS_TAG_PG_PORTAL_IP_ADDR },
{ .name = "port",		.tag = ISNS_TAG_PG_PORTAL_TCP_UDP_PORT },
{ .name = "tag",		.tag = ISNS_TAG_PG_TAG },
{ .name = "pgt",		.tag = ISNS_TAG_PG_TAG },
{ .name = "idx",		.tag = ISNS_TAG_PG_INDEX },

{ .name = "id",			.tag = ISNS_TAG_DD_ID },
{ .name = "name",		.tag = ISNS_TAG_DD_SYMBOLIC_NAME },
{ .name = "member-name",	.tag = ISNS_TAG_DD_MEMBER_ISCSI_NAME },
{ .name = "member-iscsi-idx",	.tag = ISNS_TAG_DD_MEMBER_ISCSI_INDEX },
{ .name = "member-fc-name",	.tag = ISNS_TAG_DD_MEMBER_FC_PORT_NAME },
{ .name = "member-portal-idx",	.tag = ISNS_TAG_DD_MEMBER_PORTAL_INDEX },
{ .name = "member-addr",	.tag = ISNS_TAG_DD_MEMBER_PORTAL_IP_ADDR  },
{ .name = "member-port",	.tag = ISNS_TAG_DD_MEMBER_PORTAL_TCP_UDP_PORT  },
{ .name = "features",		.tag = ISNS_TAG_DD_FEATURES },

{ .name = "name",		.tag = OPENISNS_TAG_POLICY_SPI, .alias = { "spi" }, },
{ .name = "key",		.tag = OPENISNS_TAG_POLICY_KEY },
{ .name = "entity",		.tag = OPENISNS_TAG_POLICY_ENTITY },
{ .name = "object-type",	.tag = OPENISNS_TAG_POLICY_OBJECT_TYPE },
{ .name = "node-type",		.tag = OPENISNS_TAG_POLICY_NODE_TYPE },
{ .name = "node-name",		.tag = OPENISNS_TAG_POLICY_NODE_NAME },
{ .name = "functions",		.tag = OPENISNS_TAG_POLICY_FUNCTIONS },

{ NULL }
};

/*
 * Initialize tag array
 */
static void
init_tags(void)
{
	struct tag_name	*t;

	for (t = all_attrs; t->name; ++t) {
		isns_object_template_t *tmpl;

		tmpl = isns_object_template_for_tag(t->tag);
		if (tmpl == NULL)
			isns_fatal("Bug: cannot find object type for tag %s\n",
					t->name);
		t->prefix = &all_prefixes[tmpl->iot_handle];
	}
}

/*
 * Match prefix
 */
static struct isns_tag_prefix *
find_prefix(const char *name)
{
	struct isns_tag_prefix *p;
	unsigned int	i;

	for (i = 0, p = all_prefixes; i < __ISNS_OBJECT_TYPE_MAX; ++i, ++p) {
		if (p->name && !strncmp(name, p->name, p->name_len))
			return p;
	}
	return NULL;
}

/*
 * Look up the tag for a given attribute name.
 * By default, attr names come with a disambiguating
 * prefix that defines the object type the attribute applies
 * to, such as "entity-" or "portal-". Once a context has
 * been established (ie we know the object type subsequent
 * attributes apply to), specifying the prefix is optional.
 *
 * For instance, in a portal context, "addr=10.1.1.1 port=616 name=foo"
 * specifies three portal related attributes. Whereas in a portal
 * group context, the same string would specify three portal group
 * related attributes. To disambiguate, the first attribute in
 * this list should be prefixed by "portal-" or "pg-", respectively.
 */
static uint32_t
tag_by_name(const char *name, struct isns_attr_list_parser *st)
{
	const char	*orig_name = name;
	unsigned int	nmatch = 0, i;
	struct tag_name	*t, *match[8];
	struct isns_tag_prefix *specific = NULL;

	if (all_attrs[0].prefix == NULL)
		init_tags();

	specific = find_prefix(name);
	if (specific != NULL) {
		if (st->prefix
		 && st->prefix != specific
		 && !st->multi_type_permitted) {
			isns_error("Cannot mix attributes of different types\n");
			return 0;
		}
		name += specific->name_len;
		st->prefix = specific;
	}

	for (t = all_attrs; t->name; ++t) {
		if (specific && t->prefix != specific)
			continue;
		if (!st->multi_type_permitted
		 && st->prefix && t->prefix != st->prefix)
			continue;
		if (!strcmp(name, t->name))
			goto match;
		for (i = 0; i < MAX_ALIASES && t->alias[i]; ++i) {
			if (!strcmp(name, t->alias[i]))
				goto match;
		}
		continue;

match:
		if (nmatch < 8)
			match[nmatch++] = t;
	}

	if (nmatch > 1) {
		char		conflict[128];
		unsigned int	i;

		conflict[0] = '\0';
		for (i = 0; i < nmatch; ++i) {
			if (i)
				strcat(conflict, ", ");
			t = match[i];
			strcat(conflict, t->prefix->name);
			strcat(conflict, t->name);
		}
		isns_error("tag name \"%s\" not unique in this context "
				"(could be one of %s)\n",
				orig_name, conflict);
		return 0;
	}

	if (nmatch == 0) {
		isns_error("tag name \"%s\" not known in this context\n",
				orig_name);
		return 0;
	}

	st->prefix = match[0]->prefix;
	return match[0]->tag;
}

static const char *
name_by_tag(uint32_t tag, struct isns_attr_list_parser *st)
{
	struct tag_name *t;

	for (t = all_attrs; t->name; ++t) {
		if (st->prefix && t->prefix != st->prefix)
			continue;
		if (t->tag == tag)
			return t->name;
	}
	return NULL;
}

static int
parse_one_attr(const char *name, const char *value,
		isns_attr_list_t *attrs,
		struct isns_attr_list_parser *st)
{
	isns_attr_t	*attr;
	uint32_t	tag;

	/* Special case: "portal=<address:port>" is translated to
	 * addr=<address> port=<port>
	 * If no context has been set, assume portal context.
	 */
	if (!strcasecmp(name, "portal")) {
		isns_portal_info_t portal_info;
		uint32_t	addr_tag, port_tag;

		if (st->prefix == NULL) {
			addr_tag = tag_by_name("portal-addr", st);
			port_tag = tag_by_name("portal-port", st);
		} else {
			addr_tag = tag_by_name("addr", st);
			port_tag = tag_by_name("port", st);
		}

		if (!addr_tag || !port_tag) {
			isns_error("portal=... not supported in this context\n");
			return 0;
		}
		if (value == NULL) {
			isns_attr_list_append_nil(attrs, addr_tag);
			isns_attr_list_append_nil(attrs, port_tag);
			return 1;
		}
		if (!isns_portal_parse(&portal_info, value, st->default_port))
			return 0;
		isns_portal_to_attr_list(&portal_info, addr_tag, port_tag, attrs);
		return 1;
	}

	if (!(tag = tag_by_name(name, st)))
		return 0;

	/* Special handling for key objects */
	if (tag == (uint32_t)OPENISNS_TAG_POLICY_KEY) {
		if (!value || !strcasecmp(value, "gen")) {
			if (st->generate_key == NULL) {
				isns_error("Key generation not supported in this context\n");
				return 0;
			}
			attr = st->generate_key();
		} else {
			if (st->load_key == NULL) {
				isns_error("Policy-key attribute not supported in this context\n");
				return 0;
			}
			attr = st->load_key(value);
		}
		goto append_attr;
	}

	if (value == NULL) {
		isns_attr_list_append_nil(attrs, tag);
		return 1;
	}

	attr = isns_attr_from_string(tag, value);
	if (!attr)
		return 0;

append_attr:
	isns_attr_list_append_attr(attrs, attr);
	return 1;
}

void
isns_attr_list_parser_init(struct isns_attr_list_parser *st,
				isns_object_template_t *tmpl)
{
	if (all_attrs[0].prefix == NULL)
		init_tags();

	memset(st, 0, sizeof(*st));
	if (tmpl)
		st->prefix = &all_prefixes[tmpl->iot_handle];
}

int
isns_attr_list_split(char *line, char **argv,
		__attribute__((unused))unsigned int argc_max)
{
	char		*src = line;
	unsigned int	argc = 0, quoted = 0;

	if (!line)
		return 0;

	while (1) {
		char	*dst;

		while (isspace(*src))
			++src;
		if (!*src)
			break;

		argv[argc] = dst = src;
		while (*src) {
			char cc = *src++;

			if (cc == '"') {
				quoted = !quoted;
				continue;
			}
			if (!quoted && isspace(cc)) {
				*dst = '\0';
				break;
			}
			*dst++ = cc;
		}

		if (quoted) {
			isns_error("%s: Unterminated quoted string: \"%s\"\n",
					__FUNCTION__, argv[argc]);
			return -1;
		}
		argc++;
	}

	return argc;
}

int
isns_parse_attrs(unsigned int argc, char **argv,
		isns_attr_list_t *attrs,
		struct isns_attr_list_parser *st)
{
	unsigned int	i;

	for (i = 0; i < argc; ++i) {
		char		*name, *value;

		name = argv[i];
		if ((value = strchr(name, '=')) != NULL)
			*value++ = '\0';

		if (!value && !st->nil_permitted) {
			isns_error("Missing value for atribute %s\n", name);
			return 0;
		}

		if (!parse_one_attr(name, value, attrs, st)) {
			isns_error("Unable to parse %s=%s\n", name, value);
			return 0;
		}
	}

	return 1;
}

/*
 * Query strings may contain a mix of query keys (foo=bar),
 * and requested attributes (?foo). The former are used by
 * the server in its object search, whereas the latter instruct
 * it which attributes to return.
 */
int
isns_parse_query_attrs(unsigned int argc, char **argv,
		isns_attr_list_t *keys,
		isns_attr_list_t *requested_attrs,
		struct isns_attr_list_parser *st)
{
	struct isns_attr_list_parser query_state;
	unsigned int	i;

	query_state = *st;
	query_state.multi_type_permitted = 1;

	for (i = 0; i < argc; ++i) {
		char		*name, *value;

		name = argv[i];
		if ((value = strchr(name, '=')) != NULL)
			*value++ = '\0';

		if (name[0] == '?') {
			uint32_t tag;

			if (value) {
				isns_error("No value allowed for query attribute %s\n",
						name);
				return 0;
			}

			if ((tag = tag_by_name(name + 1, &query_state)) != 0) {
				isns_attr_list_append_nil(requested_attrs, tag);
				continue;
			}
		} else {
			if (!value && !st->nil_permitted) {
				isns_error("Missing value for atribute %s\n", name);
				return 0;
			}

			if (parse_one_attr(name, value, keys, st))
				continue;
		}

		isns_error("Unable to parse %s=%s\n", name, value);
		return 0;
	}

	return 1;
}

void
isns_attr_list_parser_help(struct isns_attr_list_parser *st)
{
	isns_object_template_t *tmpl, *current = NULL;
	struct tag_name	*t;

	if (all_attrs[0].prefix == NULL)
		init_tags();

	for (t = all_attrs; t->name; ++t) {
		const isns_tag_type_t *tag_type;
		char		namebuf[64];
		const char	*help;
		unsigned int	i;

		if (st && !st->multi_type_permitted
		 && st->prefix && t->prefix != st->prefix)
			continue;

		tmpl = t->prefix->context;
		if (tmpl != current) {
			printf("\nAttributes for object type %s; using prefix %s\n",
				tmpl->iot_name, t->prefix->name);
			current = tmpl;
		}

		snprintf(namebuf, sizeof(namebuf), "%s%s", t->prefix->name, t->name);
		printf("  %-20s   ", namebuf);

		tag_type = isns_tag_type_by_id(t->tag);
		if (tag_type == NULL) {
			printf("Unknown\n");
			continue;
		}
		printf("%s (%s", tag_type->it_name,
				tag_type->it_type->it_name);

		if (tag_type->it_readonly)
			printf("; readonly");
		if (tag_type->it_multiple)
			printf("; multiple instances");
		printf(")");

		help = NULL;
		if (t->tag == (uint32_t)OPENISNS_TAG_POLICY_KEY) {
			help = "name of key file, or \"gen\" for key generation";
		} else
		if (tag_type->it_help)
			help = tag_type->it_help();

		if (help) {
			if (strlen(help) < 20)
				printf(" [%s]", help);
			else
				printf("\n%25s[%s]", "", help);
		}
		printf("\n");

		if (t->alias[0]) {
			printf("%25sAliases:", "");
			for (i = 0; i < MAX_ALIASES && t->alias[i]; ++i)
				printf(" %s", t->alias[i]);
			printf("\n");
		}
	}
}

isns_object_template_t *
isns_attr_list_parser_context(const struct isns_attr_list_parser *st)
{
	if (st->prefix)
		return st->prefix->context;
	return NULL;
}

int
isns_print_attrs(isns_object_t *obj, char **argv, unsigned int argsmax)
{
	struct isns_attr_list_parser st;
	unsigned int	i, argc = 0;

	isns_attr_list_parser_init(&st, obj->ie_template);

	for (i = 0; i < obj->ie_attrs.ial_count; ++i) {
		isns_attr_t *attr = obj->ie_attrs.ial_data[i];
		char		argbuf[512], value[512];
		const char	*name;

		name = name_by_tag(attr->ia_tag_id, &st);
		if (name == NULL)
			continue;
		if (argc + 1 >= argsmax)
			break;

		snprintf(argbuf, sizeof(argbuf), "%s%s=%s",
				st.prefix->name, name,
				isns_attr_print_value(attr, value, sizeof(value)));
		argv[argc++] = isns_strdup(argbuf);
	}

	argv[argc] = NULL;
	return argc;
}
