/* $NetBSD: iscsid_lists.c,v 1.9 2016/05/29 13:35:45 mlelstv 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_initator_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); }