/*
* Handle object visibility and scope.
*
* Copyright (C) 2007 Olaf Kirch <olaf.kirch@oracle.com>
*/
#include <stdlib.h>
#include <string.h>
#include "config.h"
#include <libisns/isns.h>
#include <libisns/attrs.h>
#include "objects.h"
#include <libisns/message.h>
#include "security.h"
#include <libisns/util.h>
#include "db.h"
struct isns_scope {
isns_db_t * ic_db;
unsigned int ic_users;
isns_object_t * ic_source_node;
isns_object_template_t * ic_query_class;
isns_object_list_t ic_dd_nodes;
isns_object_list_t ic_dd_portals;
isns_object_list_t ic_objects;
};
static int __isns_scope_collect_dd(uint32_t, void *);
/*
* Allocate an empty scope
*/
isns_scope_t *
isns_scope_alloc(isns_db_t *db)
{
isns_scope_t *scope;
scope = isns_calloc(1, sizeof(*scope));
scope->ic_db = db;
scope->ic_users = 1;
return scope;
}
isns_scope_t *
isns_scope_get(isns_scope_t *scope)
{
if (scope) {
isns_assert(scope->ic_users);
scope->ic_users++;
}
return scope;
}
void
isns_scope_release(isns_scope_t *scope)
{
if (!scope)
return;
isns_assert(scope->ic_users);
if (--(scope->ic_users))
return;
isns_object_release(scope->ic_source_node);
isns_object_list_destroy(&scope->ic_dd_nodes);
isns_object_list_destroy(&scope->ic_dd_portals);
isns_object_list_destroy(&scope->ic_objects);
isns_free(scope);
}
/*
* Get the scope for this operation
*/
isns_scope_t *
isns_scope_for_call(isns_db_t *db, const isns_simple_t *call)
{
isns_source_t *source = call->is_source;
isns_object_t *node;
isns_scope_t *scope;
uint32_t node_type;
/* FIXME use source->is_node and source->is_node_type */
/* When we get here, we already know that the client
* represents the specified source node. */
node = isns_db_lookup_source_node(db, source);
/* Allow unknown nodes to query the DB */
if (node == NULL) {
node = isns_create_storage_node2(source, 0, NULL);
if (node == NULL)
return NULL;
source->is_untrusted = 1;
}
if (isns_object_get_uint32(node, ISNS_TAG_ISCSI_NODE_TYPE, &node_type)
&& (node_type & ISNS_ISCSI_CONTROL_MASK)) {
isns_object_release(node);
return isns_scope_get(db->id_global_scope);
}
scope = isns_scope_alloc(db);
scope->ic_source_node = node;
{
isns_object_list_t members = ISNS_OBJECT_LIST_INIT;
unsigned int i;
isns_object_get_visible(node, db, &members);
isns_object_list_uniq(&members);
/* If the node is not a member of any DD, allow it
* to at least talk to itself. */
if (members.iol_count == 0)
isns_object_list_append(&members, node);
/* Sort DD members into nodes and portals */
for (i = 0; i < members.iol_count; ++i) {
isns_object_t *obj = members.iol_data[i];
if (obj->ie_state != ISNS_OBJECT_STATE_MATURE)
continue;
if (!isns_policy_validate_object_access(call->is_policy,
source, obj,
call->is_function))
continue;
if (ISNS_IS_ISCSI_NODE(obj))
isns_object_list_append(&scope->ic_dd_nodes, obj);
else
if (ISNS_IS_PORTAL(obj))
isns_object_list_append(&scope->ic_dd_portals, obj);
}
isns_object_list_destroy(&members);
}
return scope;
}
/*
* Add an object to a scope
*/
void
isns_scope_add(isns_scope_t *scope, isns_object_t *obj)
{
isns_object_list_append(&scope->ic_objects, obj);
}
int
isns_scope_remove(isns_scope_t *scope, isns_object_t *obj)
{
return isns_object_list_remove(&scope->ic_objects, obj);
}
/*
* Get all objects related through a portal group, optionally
* including the portal group objects themselves
*/
static void
__isns_scope_get_pg_related(isns_scope_t *scope,
const isns_object_t *obj,
isns_object_list_t *result)
{
isns_object_list_t temp = ISNS_OBJECT_LIST_INIT;
unsigned int i;
/* Get all portal groups related to this object */
isns_db_get_relationship_objects(scope->ic_db,
obj, ISNS_RELATION_PORTAL_GROUP, &temp);
/* Include all portals/nodes that we can reach. */
for (i = 0; i < temp.iol_count; ++i) {
isns_object_t *pg, *other;
uint32_t pgt;
pg = temp.iol_data[i];
/* Skip any portal group objects with a PG tag of 0;
* these actually deny access. */
if (!isns_object_get_uint32(pg, ISNS_TAG_PG_TAG, &pgt)
|| pgt == 0)
continue;
/* Get the other object.
* Note that isns_relation_get_other doesn't
* bump the reference count, so there's no need
* to call isns_object_release(other). */
other = isns_relation_get_other(pg->ie_relation, obj);
if (other->ie_state != ISNS_OBJECT_STATE_MATURE)
continue;
isns_object_list_append(result, other);
isns_object_list_append(result, pg);
}
isns_object_list_destroy(&temp);
}
/*
* Get all portals related to the given node.
*
* 2.2.2
* Placing Portals of a Network Entity into Discovery Domains allows
* administrators to indicate the preferred IP Portal interface through
* which storage traffic should access specific Storage Nodes of that
* Network Entity. If no Portals of a Network Entity have been placed
* into a DD, then queries scoped to that DD SHALL report all Portals of
* that Network Entity. If one or more Portals of a Network Entity have
* been placed into a DD, then queries scoped to that DD SHALL report
* only those Portals that have been explicitly placed in the DD.
*/
static void
__isns_scope_get_portals(isns_scope_t *scope,
const isns_object_t *node,
isns_object_list_t *portals,
isns_object_list_t *pgs,
int unique)
{
isns_object_list_t related = ISNS_OBJECT_LIST_INIT;
unsigned int i, specific = 0;
/* Get all portals and portal groups related to the
* given node. This will put pairs of (portal, portal-group)
* on the list.
*/
__isns_scope_get_pg_related(scope, node, &related);
/* If we're querying for our own portals, don't limit
* visibility. */
if (node == scope->ic_source_node)
goto report_all_portals;
/* Check if any of the portals is mentioned in the DD
* FIXME: There is some ambiguity over what the right
* answer is when you have two nodes (initiator, target),
* and two discovery domains linking the two. One
* DD mentions a specific portal through which target
* should be accessed; the other DD does not (allowing
* use of any portal in that entity). Which portals
* to return here?
* We go for the strict interpretation, ie if *any* DD
* restricts access to certain portals, we report only
* those.
*/
for (i = 0; i < related.iol_count; i += 2) {
isns_object_t *portal = related.iol_data[i];
if (isns_object_list_contains(&scope->ic_dd_portals, portal)) {
if (portals
&& !(unique || isns_object_list_contains(portals, portal)))
isns_object_list_append(portals, portal);
if (pgs)
isns_object_list_append(pgs,
related.iol_data[i + 1]);
specific++;
}
}
if (specific)
goto out;
report_all_portals:
/* No specific portal given for this node. Add them all. */
for (i = 0; i < related.iol_count; i += 2) {
isns_object_t *portal = related.iol_data[i];
if (portals
&& !(unique && isns_object_list_contains(portals, portal)))
isns_object_list_append(portals, portal);
if (pgs)
isns_object_list_append(pgs,
related.iol_data[i + 1]);
}
out:
isns_object_list_destroy(&related);
}
/*
* Get all nodes reachable through a given portal
* This is really the same as __isns_scope_get_portals
* minus the special casing for preferred portals.
* Still, let's put this into it's own function - the whole
* thing is already complex enough already.
*/
static void
__isns_scope_get_nodes(isns_scope_t *scope,
const isns_object_t *portal,
isns_object_list_t *nodes,
isns_object_list_t *pgs,
int unique)
{
isns_object_list_t related = ISNS_OBJECT_LIST_INIT;
unsigned int i;
/* Get all nodes and portal groups related to the
* given node. This will put pairs of (nodes, portal-group)
* on the list.
*/
__isns_scope_get_pg_related(scope, portal, &related);
for (i = 0; i < related.iol_count; i += 2) {
isns_object_t *node = related.iol_data[i];
if (nodes
&& !(unique && isns_object_list_contains(nodes, node)))
isns_object_list_append(nodes, node);
if (pgs)
isns_object_list_append(pgs,
related.iol_data[i + 1]);
}
isns_object_list_destroy(&related);
}
static void
__isns_scope_get_default_dd(isns_scope_t *scope)
{
isns_object_t *obj;
if (isns_config.ic_use_default_domain) {
obj = isns_create_default_domain();
isns_object_list_append(&scope->ic_objects, obj);
isns_object_release(obj);
}
}
/*
* Scope the query
*/
static void
__isns_scope_prepare_query(isns_scope_t *scope,
isns_object_template_t *tmpl)
{
isns_object_list_t *nodes;
unsigned int i;
/* Global and default scope have no source node; they're just
* a list of objects.
*/
if (scope->ic_source_node == NULL)
return;
if (scope->ic_query_class) {
if (scope->ic_query_class == tmpl)
return;
isns_object_list_destroy(&scope->ic_objects);
}
scope->ic_query_class = tmpl;
nodes = &scope->ic_dd_nodes;
if (tmpl == &isns_entity_template) {
for (i = 0; i < nodes->iol_count; ++i) {
isns_object_t *obj = nodes->iol_data[i];
if (obj->ie_container)
isns_object_list_append(&scope->ic_objects,
obj->ie_container);
}
} else
if (tmpl == &isns_iscsi_node_template) {
for (i = 0; i < nodes->iol_count; ++i) {
isns_object_t *obj = nodes->iol_data[i];
isns_object_list_append(&scope->ic_objects, obj);
}
} else
if (tmpl == &isns_portal_template) {
for (i = 0; i < nodes->iol_count; ++i) {
isns_object_t *obj = nodes->iol_data[i];
__isns_scope_get_portals(scope, obj,
&scope->ic_objects, NULL, 0);
}
} else
if (tmpl == &isns_iscsi_pg_template) {
for (i = 0; i < nodes->iol_count; ++i) {
isns_object_t *obj = nodes->iol_data[i];
__isns_scope_get_portals(scope, obj,
NULL, &scope->ic_objects, 0);
}
} else
if (tmpl == &isns_dd_template) {
isns_object_t *node = scope->ic_source_node;
if (node && !isns_bitvector_is_empty(node->ie_membership))
isns_bitvector_foreach(node->ie_membership,
__isns_scope_collect_dd,
scope);
else
__isns_scope_get_default_dd(scope);
}
isns_object_list_uniq(&scope->ic_objects);
}
static int
__isns_scope_collect_dd(uint32_t dd_id, void *ptr)
{
isns_scope_t *scope = ptr;
isns_object_t *dd;
dd = isns_db_vlookup(scope->ic_db, &isns_dd_template,
ISNS_TAG_DD_ID, dd_id,
0);
if (dd) {
isns_object_list_append(&scope->ic_objects, dd);
isns_object_release(dd);
}
return 0;
}
/*
* Lookup functions for scope
*/
int
isns_scope_gang_lookup(isns_scope_t *scope,
isns_object_template_t *tmpl,
const isns_attr_list_t *match,
isns_object_list_t *result)
{
isns_assert(tmpl);
if (!scope)
return 0;
__isns_scope_prepare_query(scope, tmpl);
return isns_object_list_gang_lookup(&scope->ic_objects,
tmpl, match, result);
}
/*
* Get related objects.
* This is used by the query code.
*/
void
isns_scope_get_related(isns_scope_t *scope,
const isns_object_t *origin,
unsigned int type_mask,
isns_object_list_t *result)
{
isns_object_template_t *tmpl = origin->ie_template;
isns_object_list_t nodes_result = ISNS_OBJECT_LIST_INIT;
isns_object_list_t portals_result = ISNS_OBJECT_LIST_INIT;
isns_object_list_t *members = &scope->ic_dd_nodes;
unsigned int i;
if (tmpl == &isns_entity_template) {
/* Entity: include all storage nodes contained,
* the portals through which to reach them, and
* the portal groups for those. */
for (i = 0; i < members->iol_count; ++i) {
isns_object_t *obj = members->iol_data[i];
if (obj->ie_container != origin)
continue;
isns_object_list_append(&nodes_result, obj);
__isns_scope_get_portals(scope, obj,
&portals_result,
&portals_result, 1);
}
} else
if (tmpl == &isns_iscsi_node_template) {
/* Storage node: include all portals through
* which it can be reached, and the portal
* groups for those. */
__isns_scope_get_portals(scope, origin,
&portals_result,
&portals_result, 1);
/* FIXME: Include all discovery domains the
* node is a member of. */
} else
if (tmpl == &isns_portal_template) {
/* Portal: include all storage nodes which can
* be reached through it, and the portal groups
* for those. */
__isns_scope_get_nodes(scope, origin,
&portals_result,
&portals_result, 1);
} else
if (tmpl == &isns_iscsi_pg_template) {
/* Portal group: PGs *are* a relationship, but
* unclear how this should be handled.
* Return nothing for now. */
} else
if (tmpl == &isns_dd_template) {
/* Discovery domain: no related objects. */
}
isns_object_list_append_list(result, &nodes_result);
isns_object_list_append_list(result, &portals_result);
isns_object_list_destroy(&nodes_result);
isns_object_list_destroy(&portals_result);
}
isns_object_t *
isns_scope_get_next(isns_scope_t *scope,
isns_object_template_t *tmpl,
const isns_attr_list_t *current,
const isns_attr_list_t *match)
{
if (!tmpl || !scope)
return NULL;
__isns_scope_prepare_query(scope, tmpl);
return __isns_db_get_next(&scope->ic_objects, tmpl, current, match);
}