/*	$NetBSD: iscsid_lists.c,v 1.10 2022/05/31 08:43:14 andvar Exp $	*/

/*-
 * Copyright (c) 2005,2006,2011 The NetBSD Foundation, Inc.
 * All rights reserved.
 *
 * This code is derived from software contributed to The NetBSD Foundation
 * by Wasabi Systems, Inc.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */


#include "iscsid_globals.h"

/* counter for initiator ID */
static uint32_t initiator_id = 0;

/* -------------------------------------------------------------------------- */

#if 0

/*
 * verify_session:
 *    Verify that a specific session still exists, delete it if not.
 *
 * Parameter:  The session pointer.
 */

static void
verify_session(session_t * sess)
{
	generic_entry_t *curr, *next;
	int nosess = 0;

	for (curr = sess->connections.tqh_first; curr != NULL && !nosess; curr = next) {
		next = curr->link.tqe_next;
		nosess = verify_connection((connection_t *) curr) == ISCSI_STATUS_INVALID_SESSION_ID;
	}

	if (!nosess && sess->num_connections)
		return;

	TAILQ_REMOVE(&list[SESSION_LIST].list, &sess->entry, link);
	list[SESSION_LIST].num_entries--;

	while ((curr = TAILQ_FIRST(&sess->connections)) != NULL) {
		TAILQ_REMOVE(&sess->connections, curr, link);
		free(curr);
	}
	free(sess);
}


/*
 * verify_sessions:
 *    Verify that all sessions in the list still exist.
 */

void
verify_sessions(void)
{
	generic_entry_t *curr, *next;

	for (curr = list[SESSION_LIST].list.tqh_first; curr != NULL; curr = next) {
		next = curr->link.tqe_next;
		verify_session((session_t *) curr);
	}
}

#endif

/* -------------------------------------------------------------------------- */

/*
 * find_id:
 *    Find a list element by ID.
 *
 *    Parameter:  the list head and the ID to search for
 *
 *    Returns:    The pointer to the element (or NULL if not found)
 */

generic_entry_t *
find_id(generic_list_t * head, uint32_t id)
{
	generic_entry_t *curr;

	if (!id)
		return NULL;

	TAILQ_FOREACH(curr, head, link)
		if (curr->sid.id == id)
			break;

	return curr;
}

/*
 * find_name:
 *    Find a list entry by name.
 *
 *    Parameter:  the list head and the symbolic name to search for
 *
 *    Returns:    The pointer to the entry (or NULL if not found)
 */

generic_entry_t *
find_name(generic_list_t * head, uint8_t * name)
{
	generic_entry_t *curr;

	if (!*name)
		return NULL;

	TAILQ_FOREACH(curr, head, link)
		if (strcmp((char *)curr->sid.name, (char *)name) == 0)
			break;

	return curr;
}


/*
 * find_sym_id:
 *    Find a list entry by name or numeric id.
 *
 *    Parameter:  the list head and the symbolic id to search for
 *
 *    Returns:    The pointer to the entry (or NULL if not found)
 */

generic_entry_t *
find_sym_id(generic_list_t * head, iscsid_sym_id_t * sid)
{

	if (sid->id != 0)
		return find_id(head, sid->id);

	return (sid->name[0]) ? find_name(head, sid->name) : NULL;
}


/*
 * get_id:
 *    Get the numeric ID for a symbolic ID
 *
 *    Parameter:  the list head and the symbolic id
 *
 *    Returns:    The numeric ID (0 if not found)
 */

uint32_t
get_id(generic_list_t * head, iscsid_sym_id_t * sid)
{
	generic_entry_t *ent;

	if (sid->id != 0)
		return sid->id;

	ent = find_name(head, sid->name);
	return (ent != NULL) ? ent->sid.id : 0;
}


/*
 * find_target_name:
 *    Find a target by TargetName.
 *
 *    Parameter:  the target name
 *
 *    Returns:    The pointer to the target (or NULL if not found)
 */

target_t *
find_target(iscsid_list_kind_t lst, iscsid_sym_id_t * sid)
{
	target_t *targ;

	if ((targ = (target_t *)(void *)find_sym_id (&list [lst].list, sid)) != NULL)
		return targ;
	if (lst == TARGET_LIST) {
		portal_t *portal;

		if ((portal = (void *)find_portal (sid)) != NULL)
			return portal->target;
	}
	return NULL;
}


/*
 * find_target_name:
 *    Find a target by TargetName.
 *
 *    Parameter:  the target name
 *
 *    Returns:    The pointer to the target (or NULL if not found)
 */

target_t *
find_TargetName(iscsid_list_kind_t lst, uint8_t * name)
{
	generic_entry_t *curr;
	target_t *t = NULL;

	if (lst == PORTAL_LIST)
		lst = TARGET_LIST;

	TAILQ_FOREACH(curr, &list[lst].list, link) {
		t = (void *)curr;
		if (strcmp((char *)t->TargetName, (char *)name) == 0)
			break;
	}

	/* return curr instead of t because curr==NULL if name not found */
	DEB(10, ("Find_TargetName returns %p", curr));
	return (target_t *)curr;
}


/*
 * find_portal_by_addr:
 *    Find a Portal by Address.
 *
 *    Parameter:  the associated target, and the address
 *
 *    Returns:    The pointer to the portal (or NULL if not found)
 */

portal_t *
find_portal_by_addr(target_t * target, iscsi_portal_address_t * addr)
{
	generic_entry_t *curr;
	portal_t *p = NULL;

	TAILQ_FOREACH(curr, &list[PORTAL_LIST].list, link) {
		p = (void *)curr;
		DEB(10, ("Find_portal_by_addr - addr %s port %d target %p",
				 p->addr.address,
				 p->addr.port,
				 p->target));

		if (strcmp((char *)p->addr.address, (char *)addr->address) == 0 &&
			(!addr->port || p->addr.port == addr->port) &&
			p->target == target)
			break;
	}

	/* return curr instead of p because curr==NULL if not found */
	DEB(10, ("Find_portal_by_addr returns %p", curr));
	return (portal_t *)curr;
}


/*
 * find_send_target_by_addr:
 *    Find a Send Target by Address.
 *
 *    Parameter:  the address
 *
 *    Returns:    The pointer to the portal (or NULL if not found)
 */

send_target_t *
find_send_target_by_addr(iscsi_portal_address_t * addr)
{
	generic_entry_t *curr;
	send_target_t *t = NULL;

	TAILQ_FOREACH(curr, &list[SEND_TARGETS_LIST].list, link) {
		t = (void *)curr;
		if (strcmp((char *)t->addr.address, (char *)addr->address) == 0 &&
			(!addr->port || t->addr.port == addr->port))
			break;
	}

	/* return curr instead of p because curr==NULL if not found */
	DEB(10, ("Find_send_target_by_addr returns %p", curr));
	return (send_target_t *)curr;
}


/*
 * get_list:
 *    Handle GET_LIST request: Return the list of IDs contained in the list.
 *
 *    Parameter:
 *          par         The request parameters.
 *          prsp        Pointer to address of response buffer.
 *          prsp_temp   Will be set to TRUE if buffer was allocated, FALSE
 *                      for static buffer.
 */

void
get_list(iscsid_get_list_req_t * par, iscsid_response_t ** prsp, int *prsp_temp)
{
	iscsid_get_list_rsp_t *res;
	iscsid_response_t *rsp = *prsp;
	int num;
	uint32_t *idp;
	generic_list_t *plist;
	generic_entry_t *curr;

	DEB(10, ("get_list, kind %d", par->list_kind));

	if (par->list_kind == SESSION_LIST)
		LOCK_SESSIONS;
	else if (par->list_kind >= NUM_DAEMON_LISTS) {
		rsp->status = ISCSID_STATUS_INVALID_PARAMETER;
		return;
	}

	plist = &list[par->list_kind].list;
	num = list[par->list_kind].num_entries;

	if (!num) {
		if (par->list_kind == SESSION_LIST)
			UNLOCK_SESSIONS;
		rsp->status = ISCSID_STATUS_LIST_EMPTY;
		return;
	}

	rsp = make_rsp(sizeof(iscsid_get_list_rsp_t) +
					(num - 1) * sizeof(uint32_t), prsp, prsp_temp);
	if (rsp == NULL) {
		if (par->list_kind == SESSION_LIST)
			UNLOCK_SESSIONS;
		return;
	}
	/* copy the ID of all list entries */
	res = (iscsid_get_list_rsp_t *)(void *)rsp->parameter;
	res->num_entries = num;
	idp = res->id;

	TAILQ_FOREACH(curr, plist, link)
		* idp++ = curr->sid.id;

	if (par->list_kind == SESSION_LIST)
		UNLOCK_SESSIONS;
}


/*
 * search_list:
 *    Handle SEARCH_LIST request: Search the given list for the string or
 *    address.
 *    Note: Not all combinations of list and search type make sense.
 *
 *    Parameter:
 *          par         The request parameters.
 *          prsp        Pointer to address of response buffer.
 *          prsp_temp   Will be set to TRUE if buffer was allocated, FALSE
 *                      for static buffer.
 */

void
search_list(iscsid_search_list_req_t * par, iscsid_response_t ** prsp,
			int *prsp_temp)
{
	iscsid_response_t *rsp = *prsp;
	generic_entry_t *elem = NULL;

	DEB(10, ("search_list, list_kind %d, search_kind %d",
			 par->list_kind, par->search_kind));

	if (par->list_kind == SESSION_LIST)
		LOCK_SESSIONS;
	else if (par->list_kind >= NUM_DAEMON_LISTS) {
		rsp->status = ISCSID_STATUS_INVALID_PARAMETER;
		return;
	}

	if (!list[par->list_kind].num_entries) {
		if (par->list_kind == SESSION_LIST)
			UNLOCK_SESSIONS;
		rsp->status = ISCSID_STATUS_NOT_FOUND;
		return;
	}

	switch (par->search_kind) {
	case FIND_ID:
		elem = find_id(&list[par->list_kind].list, par->intval);
		break;

	case FIND_NAME:
		elem = find_name(&list[par->list_kind].list, par->strval);
		break;

	case FIND_TARGET_NAME:
		switch (par->list_kind) {
		case TARGET_LIST:
		case PORTAL_LIST:
		case SEND_TARGETS_LIST:
			elem = (void *)find_TargetName(par->list_kind,
														par->strval);
			break;

		case SESSION_LIST:
			TAILQ_FOREACH(elem, &list[SESSION_LIST].list, link)
				if (strcmp((char *)((session_t *)(void *)elem)->target.TargetName,
							(char *)par->strval) == 0)
					break;
			break;

		default:
			rsp->status = ISCSID_STATUS_INVALID_PARAMETER;
			break;
		}
		break;

	case FIND_ADDRESS:
		switch (par->list_kind) {
		case PORTAL_LIST:
			TAILQ_FOREACH(elem, &list[PORTAL_LIST].list, link) {
				portal_t *p = (void *)elem;
				if (strcmp((char *)p->addr.address, (char *)par->strval) == 0 &&
					(!par->intval ||
					 p->addr.port == par->intval))
					break;
			}
			break;

		case SEND_TARGETS_LIST:
			TAILQ_FOREACH(elem, &list[SEND_TARGETS_LIST].list, link) {
				send_target_t *t = (void *)elem;
				if (strcmp((char *)t->addr.address,
							(char *)par->strval) == 0 &&
					(!par->intval ||
					 t->addr.port == par->intval))
					break;
			}
			break;

		case ISNS_LIST:
			TAILQ_FOREACH(elem, &list[ISNS_LIST].list, link) {
				isns_t *i = (void *)elem;
				if (strcmp((char *)i->address, (char *)par->strval) == 0 &&
					(!par->intval || i->port == par->intval))
					break;
			}
			break;

		default:
			rsp->status = ISCSID_STATUS_INVALID_PARAMETER;
			break;
		}
		break;

	default:
		rsp->status = ISCSID_STATUS_INVALID_PARAMETER;
		return;
	}

	if (elem == NULL) {
		if (par->list_kind == SESSION_LIST)
			UNLOCK_SESSIONS;
		rsp->status = ISCSID_STATUS_NOT_FOUND;
		return;
	}

	rsp = make_rsp(sizeof(iscsid_sym_id_t), prsp, prsp_temp);
	if (rsp == NULL) {
		if (par->list_kind == SESSION_LIST)
			UNLOCK_SESSIONS;
		return;
	}

	(void) memcpy(rsp->parameter, &elem->sid, sizeof(elem->sid));
	if (par->list_kind == SESSION_LIST)
		UNLOCK_SESSIONS;
}


/*
 * get_session_list:
 *    Handle GET_SESSION_LIST request: Return a list of sessions complete
 *    with basic session info.
 *
 *    Parameter:
 *          prsp        Pointer to address of response buffer.
 *          prsp_temp   Will be set to TRUE if buffer was allocated, FALSE
 *                      for static buffer.
 */

void
get_session_list(iscsid_response_t ** prsp, int *prsp_temp)
{
	iscsid_get_session_list_rsp_t *res;
	iscsid_response_t *rsp = *prsp;
	iscsid_session_list_entry_t *ent;
	generic_list_t *plist;
	generic_entry_t *curr;
	session_t *sess;
	connection_t *conn;
	int num;

	DEB(10, ("get_session_list"));

	LOCK_SESSIONS;
	plist = &list[SESSION_LIST].list;
	num = list[SESSION_LIST].num_entries;

	if (!num) {
		UNLOCK_SESSIONS;
		rsp->status = ISCSID_STATUS_LIST_EMPTY;
		return;
	}

	rsp = make_rsp(sizeof(iscsid_get_session_list_rsp_t) +
				   (num - 1) * sizeof(iscsid_session_list_entry_t),
					prsp, prsp_temp);
	if (rsp == NULL) {
		UNLOCK_SESSIONS;
		return;
	}
	/* copy the ID of all list entries */
	res = (iscsid_get_session_list_rsp_t *)(void *)rsp->parameter;
	res->num_entries = num;
	ent = res->session;

	TAILQ_FOREACH(curr, plist, link) {
		sess = (session_t *)(void *)curr;
		conn = (connection_t *)(void *)TAILQ_FIRST(&sess->connections);

		ent->session_id = sess->entry.sid;
		ent->num_connections = sess->num_connections;
		if (conn) {
			ent->first_connection_id = conn->entry.sid.id;
			ent->portal_id = conn->portal.sid.id;
			ent->initiator_id = conn->initiator_id;
		} else {
			ent->first_connection_id = 0;
			ent->portal_id = 0;
			ent->initiator_id = 0;
		}
		ent++;
	}
	UNLOCK_SESSIONS;
}

/*
 * get_connection_list:
 *    Handle GET_CONNECTION_LIST request: Return a list of connections
 *    for a session.
 *
 *    Parameter:
 *          prsp        Pointer to address of response buffer.
 *          prsp_temp   Will be set to TRUE if buffer was allocated, FALSE
 *                      for static buffer.
 */

void
get_connection_list(iscsid_sym_id_t *req, iscsid_response_t **prsp,
					int *prsp_temp)
{
	iscsid_get_connection_list_rsp_t *res;
	iscsid_response_t *rsp = *prsp;
	iscsid_connection_list_entry_t *ent;
	generic_entry_t *curr;
	session_t *sess;
	connection_t *conn;
	int num;

	DEB(10, ("get_connection_list"));

	LOCK_SESSIONS;
	if ((sess = find_session(req)) == NULL) {
		UNLOCK_SESSIONS;
		rsp->status = ISCSID_STATUS_INVALID_SESSION_ID;
		return;
	}

	num = sess->num_connections;
	rsp = make_rsp(sizeof(iscsid_get_connection_list_rsp_t) +
				   (num - 1) * sizeof(iscsid_connection_list_entry_t),
					prsp, prsp_temp);
	if (rsp == NULL) {
		UNLOCK_SESSIONS;
		return;
	}
	/* copy the ID of all list entries */
	res = (iscsid_get_connection_list_rsp_t *)(void *)rsp->parameter;
	res->num_connections = num;
	ent = res->connection;

	TAILQ_FOREACH(curr, &sess->connections, link) {
		conn = (connection_t *)(void *)curr;
		ent->connection_id = conn->entry.sid;
		ent->target_portal_id = conn->portal.sid;
		ent->target_portal = conn->portal.addr;
		ent++;
	}
	UNLOCK_SESSIONS;
}


/*
 * get_connection_info:
 *    Handle GET_CONNECTION_INFO request: Return information about a connection
 *
 *    Parameter:
 *          par         The request parameters.
 *          prsp        Pointer to address of response buffer.
 *          prsp_temp   Will be set to TRUE if buffer was allocated, FALSE
 *                      for static buffer.
 */

void
get_connection_info(iscsid_get_connection_info_req_t * req,
					iscsid_response_t ** prsp, int *prsp_temp)
{
	iscsid_get_connection_info_rsp_t *res;
	iscsid_response_t *rsp = *prsp;
	session_t *sess;
	connection_t *conn;
	initiator_t *init = NULL;

	DEB(10, ("get_connection_info, session %d, connection %d",
			 req->session_id.id, req->connection_id.id));

	LOCK_SESSIONS;
	if ((sess = find_session(&req->session_id)) == NULL) {
		UNLOCK_SESSIONS;
		rsp->status = ISCSID_STATUS_INVALID_SESSION_ID;
		return;
	}
	if (!req->connection_id.id && !req->connection_id.name[0]) {
		conn = (connection_t *)(void *)TAILQ_FIRST(&sess->connections);
	} else if ((conn = find_connection(sess, &req->connection_id)) == NULL) {
		UNLOCK_SESSIONS;
		rsp->status = ISCSID_STATUS_INVALID_CONNECTION_ID;
		return;
	}

	rsp = make_rsp(sizeof(iscsid_get_connection_info_rsp_t), prsp, prsp_temp);
	if (rsp == NULL) {
		UNLOCK_SESSIONS;
		return;
	}

	if (conn && conn->initiator_id)
		init = find_initiator_id(conn->initiator_id);

	res = (iscsid_get_connection_info_rsp_t *)(void *)rsp->parameter;

	res->session_id = sess->entry.sid;
	if (conn) {
		res->connection_id = conn->entry.sid;
		res->target_portal_id = conn->portal.sid;
		res->target_portal = conn->portal.addr;
		strlcpy((char *)res->TargetName, (char *)conn->target.TargetName,
			sizeof(res->TargetName));
		strlcpy((char *)res->TargetAlias, (char *)conn->target.TargetAlias,
			sizeof(res->TargetAlias));
	} else {
		res->connection_id.id = 0;
		res->connection_id.name[0] = '\0';
		res->target_portal_id.id = 0;
		res->target_portal_id.name[0] = '\0';
		memset(&res->target_portal, 0, sizeof(res->target_portal));
		memset(&res->TargetName, 0, sizeof(res->TargetName));
		memset(&res->TargetAlias, 0, sizeof(res->TargetAlias));
	}
	if (init != NULL) {
		res->initiator_id = init->entry.sid;
		strlcpy((char *)res->initiator_address, (char *)init->address,
			sizeof(res->initiator_address));
	}
	UNLOCK_SESSIONS;
}

/* ------------------------------------------------------------------------- */

/*
 * find_initiator_by_addr:
 *    Find an Initiator Portal by Address.
 *
 *    Parameter:  the address
 *
 *    Returns:    The pointer to the portal (or NULL if not found)
 */

static initiator_t *
find_initiator_by_addr(uint8_t * addr)
{
	generic_entry_t *curr;
	initiator_t *i = NULL;

	TAILQ_FOREACH(curr, &list[INITIATOR_LIST].list, link) {
		i = (void *)curr;
		if (strcmp((char *)i->address, (char *)addr) == 0)
			break;
	}

	/* return curr instead of i because if not found, curr==NULL */
	DEB(9, ("Find_initiator_by_addr returns %p", curr));
	return (initiator_t *)curr;
}


/*
 * add_initiator_portal:
 *    Add an initiator portal.
 *
 *    Parameter:
 *          par         The request parameters.
 *          prsp        Pointer to address of response buffer.
 *          prsp_temp   Will be set to TRUE if buffer was allocated, FALSE
 *                      for static buffer.
 */

void
add_initiator_portal(iscsid_add_initiator_req_t *par, iscsid_response_t **prsp,
					 int *prsp_temp)
{
	iscsid_add_initiator_rsp_t *res;
	iscsid_response_t *rsp = *prsp;
	initiator_t *init;

	DEB(9, ("AddInitiatorPortal '%s' (name '%s')", par->address, par->name));

	if (find_initiator_by_addr(par->address) != NULL) {
		rsp->status = ISCSID_STATUS_DUPLICATE_ENTRY;
		return;
	}

	if (find_initiator_name(par->name) != NULL) {
		rsp->status = ISCSID_STATUS_DUPLICATE_NAME;
		return;
	}

	if ((init = calloc(1, sizeof(*init))) == NULL) {
		rsp->status = ISCSID_STATUS_NO_RESOURCES;
		return;
	}

	DEB(9, ("AddInitiatorPortal initiator_id = %d", initiator_id));

	for (initiator_id++;
		 !initiator_id || find_initiator_id(initiator_id) != NULL;)
		initiator_id++;

	init->entry.sid.id = initiator_id;
	strlcpy((char *)init->entry.sid.name, (char *)par->name, sizeof(init->entry.sid.name));
	strlcpy((char *)init->address, (char *)par->address, sizeof(init->address));

	rsp = make_rsp(sizeof(iscsid_add_initiator_rsp_t), prsp, prsp_temp);
	if (rsp == NULL)
		return;

	LOCK_SESSIONS;
	TAILQ_INSERT_TAIL(&list[INITIATOR_LIST].list, &init->entry, link);
	list[INITIATOR_LIST].num_entries++;
	UNLOCK_SESSIONS;

	res = (iscsid_add_initiator_rsp_t *)(void *)rsp->parameter;
	res->portal_id = init->entry.sid.id;
}


/*
 * remove_initiator_portal:
 *    Handle REMOVE_INITIATOR request: Removes an initiator entry.
 *
 *    Parameter:
 *          par         The request parameter containing the ID.
 *
 *    Returns:     status
 */

uint32_t
remove_initiator_portal(iscsid_sym_id_t * par)
{
	initiator_t *init;

	if ((init = find_initiator(par)) == NULL)
		return ISCSID_STATUS_INVALID_INITIATOR_ID;

	LOCK_SESSIONS;
	list[INITIATOR_LIST].num_entries--;

	TAILQ_REMOVE(&list[INITIATOR_LIST].list, &init->entry, link);
	UNLOCK_SESSIONS;

	free(init);

	return ISCSID_STATUS_SUCCESS;
}



/*
 * get_initiator_portal:
 *    Handle GET_INITIATOR_PORTAL request: Return information about the given
 *    initiator portal.
 *
 *    Parameter:
 *          par         The request parameters.
 *          prsp        Pointer to address of response buffer.
 *          prsp_temp   Will be set to TRUE if buffer was allocated, FALSE
 *                      for static buffer.
 */

void
get_initiator_portal(iscsid_sym_id_t *par, iscsid_response_t **prsp,
					 int *prsp_temp)
{
	iscsid_get_initiator_rsp_t *res;
	iscsid_response_t *rsp = *prsp;
	initiator_t *init;

	DEB(10, ("get_initiator_portal, id %d (%s)", par->id, par->name));

	if ((init = find_initiator(par)) == NULL) {
		rsp->status = ISCSID_STATUS_INVALID_INITIATOR_ID;
		return;
	}

	rsp = make_rsp(sizeof(iscsid_get_initiator_rsp_t), prsp, prsp_temp);
	if (rsp == NULL)
		return;

	res = (iscsid_get_initiator_rsp_t *)(void *)rsp->parameter;
	res->portal_id = init->entry.sid;
	strlcpy((char *)res->address, (char *)init->address, sizeof(res->address));
}


/*
 * select_initiator:
 *    Select the initiator portal to use.
 *    Selects the portal with the least number of active connections.
 *
 *    Returns:
 *       Pointer to the portal, NULL if no portals are defined.
 *
 *    NOTE: Called with session list locked, so don't lock again.
 */

initiator_t *
select_initiator(void)
{
	generic_entry_t *curr;
	initiator_t *imin = NULL;
	uint32_t ccnt = 64 * 1024;	/* probably not more than 64k connections... */

	if (!list[INITIATOR_LIST].num_entries)
		return NULL;

	TAILQ_FOREACH(curr, &list[INITIATOR_LIST].list, link) {
		initiator_t *i = (void *)curr;
		if ((i->active_connections < ccnt)) {
			ccnt = i->active_connections;
			imin = i;
		}
	}
	return imin;
}

/* ------------------------------------------------------------------------- */

/*
 * event_kill_session:
 *    Handle SESSION_TERMINATED event: Remove session and all associated
 *    connections.
 *
 *    Parameter:
 *          sid         Session ID
 */

void
event_kill_session(uint32_t sid)
{
	session_t *sess;
	connection_t *conn;
	portal_t *portal;
	initiator_t *init;

	LOCK_SESSIONS;

	sess = find_session_id(sid);

	if (sess == NULL) {
		UNLOCK_SESSIONS;
		return;
	}

	TAILQ_REMOVE(&list[SESSION_LIST].list, &sess->entry, link);
	list[SESSION_LIST].num_entries--;

	UNLOCK_SESSIONS;

	while ((conn = (connection_t *)(void *)TAILQ_FIRST(&sess->connections)) != NULL) {
		TAILQ_REMOVE(&sess->connections, &conn->entry, link);

		portal = find_portal_id(conn->portal.sid.id);
		if (portal != NULL)
			portal->active_connections--;

		init = find_initiator_id(conn->initiator_id);
		if (init != NULL)
			init->active_connections--;

		free(conn);
	}
	free(sess);
}


/*
 * event_kill_connection:
 *    Handle CONNECTION_TERMINATED event: Remove connection from session.
 *
 *    Parameter:
 *          sid         Session ID
 *          cid         Connection ID
 */

void
event_kill_connection(uint32_t sid, uint32_t cid)
{
	session_t *sess;
	connection_t *conn;
	portal_t *portal;
	initiator_t *init;

	LOCK_SESSIONS;

	sess = find_session_id(sid);
	if (sess == NULL) {
		UNLOCK_SESSIONS;
		return;
	}

	conn = find_connection_id(sess, cid);
	if (conn == NULL) {
		UNLOCK_SESSIONS;
		return;
	}

	TAILQ_REMOVE(&sess->connections, &conn->entry, link);
	sess->num_connections--;

	init = find_initiator_id(conn->initiator_id);
	if (init != NULL)
		init->active_connections--;

	UNLOCK_SESSIONS;

	portal = find_portal_id(conn->portal.sid.id);
	if (portal != NULL)
		portal->active_connections--;

	free(conn);
}
