2108 lines
55 KiB
C
2108 lines
55 KiB
C
/**************************************************************************
|
|
* *
|
|
* Copyright (C) 1992-1995 Silicon Graphics, Inc. *
|
|
* *
|
|
* These coded instructions, statements, and computer programs contain *
|
|
* unpublished proprietary information of Silicon Graphics, Inc., and *
|
|
* are protected by Federal copyright law. They may not be disclosed *
|
|
* to third parties or copied or duplicated in any form, in whole or *
|
|
* in part, without the prior written consent of Silicon Graphics, Inc. *
|
|
* *
|
|
**************************************************************************/
|
|
|
|
#ident "$Revision: 1.27 $"
|
|
|
|
#include <sys/types.h>
|
|
#include <sys/debug.h>
|
|
#include <sys/idbg.h>
|
|
#include <sys/idbgentry.h>
|
|
#include <sys/sema.h>
|
|
#include <sys/graph.h>
|
|
|
|
#if !_USER_MODE_TEST
|
|
#include <sys/kmem.h>
|
|
#include <sys/systm.h>
|
|
#endif /* !_USER_MODE_TEST */
|
|
|
|
#include <sys/strtbl.h>
|
|
#include "graph_private.h"
|
|
|
|
#if DEBUG
|
|
static void VERTEX_REF(vertex_hdl_t, graph_vertex_t *);
|
|
static int VERTEX_UNREF(vertex_hdl_t, graph_vertex_t *);
|
|
#endif
|
|
|
|
/* initialized from graph_vertex_per_group by _io_sanity() */
|
|
int graph_vertex_per_group_log2;
|
|
|
|
/*
|
|
** Generic graph library.
|
|
**
|
|
** Supports directed graphs consisting of vertices and named edges. At any
|
|
** vertex, the caller can store two kinds of information: "labelled" and
|
|
** "indexed". In both cases, information is a single long (which may hold a
|
|
** pointer to an arbitrary structure). Labelled information provides arbitrary
|
|
** flexibility to the caller; but to retrieve labelled information the graph
|
|
** module must do label comparisons. Indexed information can be used in cases
|
|
** where retrieval time is important; but indexed information preallocates a
|
|
** small amount of space for every vertex. The current implementation of
|
|
** indexed information is fast, non-blocking, and lock-free.
|
|
**
|
|
** Routines for accessing the graph are grouped into the following categories:
|
|
** operations on GRAPHS
|
|
** operations on VERTICES
|
|
** operations on EDGES
|
|
** operations on LABELLED INFORMATION
|
|
** operations on INDEXED INFORMATION
|
|
** operations on paths
|
|
**
|
|
** The graph module deals with strings that look like pathnames, but each
|
|
** component in the path represents an edge name. Given a starting vertex
|
|
** and a path, one can follow edges to a target vertex.
|
|
**
|
|
** One important goal of the implementation is to conserve space -- especially
|
|
** space that scales with the size of the graph (number of vertices).
|
|
**
|
|
** All structures are expected to be vastly read-only, and all accesses are
|
|
** brief, so locking is handled with multi-reader spinlocks. If locking is
|
|
** modified, it should be kept in mind that we must be able to *retrieve*
|
|
** information at interrupt level.
|
|
*/
|
|
|
|
#define MRLOCK_INIT(lockp, name, seq) mrlock_init(lockp, MRLOCK_BARRIER,(name), seq)
|
|
#define MRLOCK_ACCESS(lockp) {mraccess(lockp); s=s;}
|
|
#define MRLOCK_UPDATE(lockp) {mrupdate(lockp); s=s;}
|
|
#define MRLOCK_DONE_ACCESS(lockp) {mraccunlock(lockp); s=s;}
|
|
#define MRLOCK_DONE_UPDATE(lockp) {mrunlock(lockp); s=s;}
|
|
#define MRLOCK_FREE(lockp) mrfree(lockp)
|
|
|
|
|
|
/*****************************/
|
|
/* INTERNAL support routines */
|
|
/*****************************/
|
|
|
|
/********************************************************************/
|
|
/* Internal routines for dealing with vertices and vertex handles: */
|
|
/* vhandle_get_vertex, vertex_allocate, vertex_free */
|
|
/********************************************************************/
|
|
|
|
/*
|
|
** graph_vhandle_get_vertex perform basic validity checks on a vertex handle.
|
|
** If it's valid, return a pointer to the vertex, a pointer to the vertex group
|
|
** that it's in, and the groupid and grpidx.
|
|
**
|
|
** Locking is handled by caller.
|
|
*/
|
|
static graph_error_t
|
|
graph_vhandle_get_vertex(graph_t *graph,
|
|
vertex_hdl_t vertex_handle,
|
|
graph_vertex_t **vertex,
|
|
graph_vertex_group_t **vertex_group,
|
|
int *groupid,
|
|
int *grpidx)
|
|
{
|
|
graph_vertex_t *loc_vertex;
|
|
int loc_groupid, loc_grpidx;
|
|
graph_vertex_group_t *loc_vertex_group;
|
|
|
|
if (graph == NULL)
|
|
return(GRAPH_BAD_PARAM);
|
|
|
|
if (vertex_handle == GRAPH_VERTEX_NONE)
|
|
return(GRAPH_BAD_PARAM);
|
|
|
|
if (vertex_handle >= graph->graph_vertex_handle_limit)
|
|
return(GRAPH_HIT_LIMIT);
|
|
|
|
loc_groupid = HANDLE_TO_GROUPID(graph, vertex_handle);
|
|
loc_grpidx = HANDLE_TO_GRPIDX(graph, vertex_handle);
|
|
loc_vertex_group = &graph->graph_vertex_group[loc_groupid];
|
|
loc_vertex = GRPIDX_TO_VERTEX(graph, loc_groupid, loc_grpidx);
|
|
|
|
if (VERTEX_ISFREE(loc_vertex) || VERTEX_ISNEW(loc_vertex))
|
|
return(GRAPH_NOT_FOUND);
|
|
|
|
*vertex = loc_vertex;
|
|
*vertex_group = loc_vertex_group;
|
|
*groupid = loc_groupid;
|
|
*grpidx = loc_grpidx;
|
|
|
|
return(GRAPH_SUCCESS);
|
|
}
|
|
|
|
|
|
#define NOT_REACHED 0
|
|
|
|
/*
|
|
** graph_vertex_allocate finds an unused vertex handle to represent the specified
|
|
** vertex. If we're out of handle slots (or close to it), increase the size of the
|
|
** graph.
|
|
*/
|
|
static graph_error_t
|
|
graph_vertex_allocate( graph_t *graph,
|
|
vertex_hdl_t *new_vertex_handle,
|
|
graph_vertex_t **new_vertex)
|
|
{
|
|
int grpidx, groupid;
|
|
int i, num_idx;
|
|
int group_rotor, group_limit;
|
|
graph_vertex_group_t *vertex_group;
|
|
graph_vertex_t *new_vertex_array, *vertex;
|
|
int old_vertex_limit, new_vertex_limit;
|
|
arbitrary_info_t *info_IDX_ptr;
|
|
int s;
|
|
|
|
if (graph == NULL)
|
|
return(GRAPH_BAD_PARAM);
|
|
|
|
if ((new_vertex_handle == NULL) && (new_vertex == NULL))
|
|
return(GRAPH_BAD_PARAM);
|
|
|
|
startover:
|
|
old_vertex_limit = graph->graph_vertex_handle_limit;
|
|
|
|
/*
|
|
* Search for unused vertex_handle.
|
|
*/
|
|
group_rotor = graph->graph_group_rotor;
|
|
group_limit = HANDLE_TO_GROUPID(graph, old_vertex_limit-1);
|
|
|
|
for (groupid=group_rotor; groupid<=group_limit; groupid++) {
|
|
|
|
vertex_group = &graph->graph_vertex_group[groupid];
|
|
|
|
/* Peek to see if this group looks full */
|
|
if (vertex_group->group_count == GRAPH_VERTEX_PER_GROUP)
|
|
continue;
|
|
|
|
MRLOCK_UPDATE(&vertex_group->group_mrlock);
|
|
if (vertex_group->group_count == GRAPH_VERTEX_PER_GROUP) {
|
|
MRLOCK_DONE_UPDATE(&vertex_group->group_mrlock);
|
|
continue;
|
|
}
|
|
|
|
/* We're committed to allocating a vertex from this group. */
|
|
for (grpidx=0; grpidx<GRAPH_VERTEX_PER_GROUP; grpidx++) {
|
|
vertex = GRPIDX_TO_VERTEX(graph, groupid, grpidx);
|
|
if (VERTEX_ISFREE(vertex))
|
|
goto found;
|
|
}
|
|
|
|
ASSERT(NOT_REACHED);
|
|
}
|
|
|
|
for (groupid=0; groupid<group_rotor; groupid++) {
|
|
|
|
vertex_group = &graph->graph_vertex_group[groupid];
|
|
|
|
/* Peek to see if this group looks full */
|
|
if (vertex_group->group_count == GRAPH_VERTEX_PER_GROUP)
|
|
continue;
|
|
|
|
MRLOCK_UPDATE(&vertex_group->group_mrlock);
|
|
if (vertex_group->group_count == GRAPH_VERTEX_PER_GROUP) {
|
|
MRLOCK_DONE_UPDATE(&vertex_group->group_mrlock);
|
|
continue;
|
|
}
|
|
|
|
/* We're commiteed to allocating a vertex from this group. */
|
|
for (grpidx=0; grpidx<GRAPH_VERTEX_PER_GROUP; grpidx++) {
|
|
vertex = GRPIDX_TO_VERTEX(graph, groupid, grpidx);
|
|
if (VERTEX_ISFREE(vertex))
|
|
goto found;
|
|
}
|
|
|
|
ASSERT(NOT_REACHED);
|
|
}
|
|
|
|
/*
|
|
* If we're trying to add a new vertex and we've already reached the
|
|
* current limit (or we're at least close to the limit), we need to
|
|
* expand the graph by adding a new group of vertices. The graph
|
|
* might not actually be full, since we cheated and peeked at some
|
|
* fields without holding a lock. Close enough, though -- in the
|
|
* worst case we'll allocate a new group when we didn't really need
|
|
* to.
|
|
*/
|
|
new_vertex_limit = old_vertex_limit + GRAPH_VERTEX_PER_GROUP;
|
|
|
|
/*
|
|
* Guard against unbounded growth of the graph. Something's wrong.
|
|
*/
|
|
if (new_vertex_limit > graph->graph_num_group*GRAPH_VERTEX_PER_GROUP)
|
|
return(GRAPH_HIT_LIMIT);
|
|
|
|
new_vertex_array = graph_allocate(SIZE_GROUP_VERTEX_ARRAY(graph));
|
|
if (new_vertex_array == NULL)
|
|
return(GRAPH_CANNOT_ALLOC);
|
|
|
|
MRLOCK_UPDATE(&graph->graph_mrlock);
|
|
|
|
if (graph->graph_vertex_handle_limit != old_vertex_limit) {
|
|
/*
|
|
* Things changed pretty drastically while we worked.
|
|
* We might not need a new group after all, so free up
|
|
* the space we just allocated and start over.
|
|
*/
|
|
MRLOCK_DONE_UPDATE(&graph->graph_mrlock);
|
|
graph_free(new_vertex_array);
|
|
goto startover;
|
|
}
|
|
|
|
/*
|
|
* At this point:
|
|
* we have the graph mrlock
|
|
* we know that we had some trouble allocating a handle,
|
|
* we know that the graph hasn't grown at all since we had that trouble,
|
|
* and we've already allocated space for a new group of vertices
|
|
*
|
|
* Go ahead and add the new group!
|
|
*/
|
|
|
|
groupid = HANDLE_TO_GROUPID(graph, old_vertex_limit);
|
|
vertex_group = &(graph->graph_vertex_group[groupid]);
|
|
vertex_group->group_vertex_array = new_vertex_array;
|
|
|
|
graph->graph_vertex_handle_limit = new_vertex_limit;
|
|
graph->graph_group_rotor = groupid;
|
|
atomicAddUlong(&graph->graph_generation, 1);
|
|
|
|
MRLOCK_DONE_UPDATE(&graph->graph_mrlock);
|
|
/*
|
|
* Now that we've increased the number of vertices that can be accomodated,
|
|
* start from the beginning and see if we can find a free one.
|
|
*/
|
|
goto startover;
|
|
|
|
|
|
found:
|
|
/*
|
|
* We found a free vertex: It's the "grpidx" vertex in group "groupid".
|
|
* Initialize it and return its handle.
|
|
*/
|
|
ASSERT(&graph->graph_vertex_group[groupid] == vertex_group);
|
|
|
|
graph->graph_group_rotor = groupid;
|
|
|
|
vertex->v_num_edge = 0;
|
|
vertex->v_num_info_LBL = 0;
|
|
vertex->v_refcnt = 1;
|
|
|
|
num_idx = graph->graph_num_index;
|
|
info_IDX_ptr = VERTEX_INFO_IDX_PTR(vertex);
|
|
for (i=0; i<num_idx; i++, info_IDX_ptr++)
|
|
*info_IDX_ptr = 0;
|
|
|
|
vertex->v_info_list = GRAPH_VERTEX_NEW;
|
|
|
|
vertex_group->group_count++;
|
|
atomicAddInt(&graph->graph_num_vertex, 1);
|
|
atomicAddUlong(&graph->graph_generation, 1);
|
|
|
|
MRLOCK_DONE_UPDATE(&vertex_group->group_mrlock);
|
|
|
|
if (new_vertex_handle != NULL)
|
|
*new_vertex_handle = GRPIDX_TO_HANDLE(groupid, grpidx);
|
|
|
|
if (new_vertex != NULL)
|
|
*new_vertex = vertex;
|
|
|
|
return(GRAPH_SUCCESS);
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
** graph_vertex_free frees a previously-used vertex. The vertex can no longer
|
|
** be referenced. The graph module may reallocate the associated handle for
|
|
** new vertices. This routine is called only when the vertex' reference count
|
|
** drops to 0.
|
|
**
|
|
** Locking is handled by caller.
|
|
*/
|
|
static void
|
|
graph_vertex_free( graph_t *graph,
|
|
vertex_hdl_t vhdl)
|
|
{
|
|
graph_vertex_t *vertex;
|
|
graph_vertex_group_t *vertex_group;
|
|
graph_error_t status;
|
|
graph_edge_t *edge_list;
|
|
int i, num_edge;
|
|
int junk_groupid;
|
|
int junk_grpidx;
|
|
|
|
ASSERT(graph != NULL);
|
|
|
|
status = graph_vhandle_get_vertex(graph, vhdl, &vertex,
|
|
&vertex_group, &junk_groupid, &junk_grpidx);
|
|
|
|
if (status != GRAPH_SUCCESS)
|
|
return;
|
|
|
|
ASSERT(vertex != NULL);
|
|
ASSERT(vertex->v_refcnt == 0);
|
|
|
|
/*
|
|
** Free any lingering outgoing edges simply by handling reference counts.
|
|
** Memory consumed by all edges (and LBL info) is released en masse, below.
|
|
*/
|
|
edge_list = VERTEX_EDGE_PTR(vertex);
|
|
num_edge = vertex->v_num_edge;
|
|
for (i=0; i<num_edge; i++) {
|
|
vertex_hdl_t target_vertex = edge_list[i].e_vertex;
|
|
graph_vertex_unref(graph, target_vertex);
|
|
}
|
|
vertex->v_num_edge = 0; /* sanity */
|
|
|
|
if (VERTEX_HASINFO(vertex))
|
|
graph_free(vertex->v_info_list);
|
|
|
|
vertex->v_info_list = GRAPH_VERTEX_FREE;
|
|
|
|
ASSERT(vertex_group->group_count > 0);
|
|
vertex_group->group_count--;
|
|
atomicAddInt(&graph->graph_num_vertex, -1);
|
|
atomicAddUlong(&graph->graph_generation, 1);
|
|
}
|
|
|
|
|
|
|
|
|
|
/***********************************/
|
|
/* EXPORTED graph module functions */
|
|
/***********************************/
|
|
|
|
|
|
/*************************************************************/
|
|
/* Operations on GRAPHS: create, destroy, summary_get, visit */
|
|
/*************************************************************/
|
|
|
|
graph_t *current_graph = NULL;
|
|
|
|
/*
|
|
** graph_create instantiates a new instance of a graph with the specified attributes.
|
|
*/
|
|
graph_error_t
|
|
graph_create( graph_attr_t *graph_attributes,
|
|
graph_t **graph,
|
|
int num_vertices)
|
|
{
|
|
char *name;
|
|
unsigned int num_idx;
|
|
graph_t *new_graph;
|
|
graph_vertex_group_t *vertex_group;
|
|
graph_vertex_t *new_vertex_array;
|
|
int groupid;
|
|
uint graph_num_group;
|
|
|
|
if (num_vertices <= 0)
|
|
graph_num_group = DEFAULT_GRAPH_NUM_GROUP;
|
|
else
|
|
graph_num_group = (num_vertices+GRAPH_VERTEX_PER_GROUP-1) / GRAPH_VERTEX_PER_GROUP;
|
|
|
|
if (graph_attributes == NULL)
|
|
return(GRAPH_BAD_PARAM);
|
|
|
|
if (graph == NULL)
|
|
return(GRAPH_BAD_PARAM);
|
|
|
|
/* Verify that graph_vertex_group is at the END of the graph_s structure */
|
|
ASSERT(sizeof(graph_t) == (unsigned long)&(((graph_t *)0)->graph_vertex_group) + sizeof(graph_vertex_group_t));
|
|
|
|
new_graph = graph_allocate(sizeof(graph_t) + (graph_num_group-1)*sizeof(graph_vertex_group_t));
|
|
if (new_graph == NULL)
|
|
return(GRAPH_CANNOT_ALLOC);
|
|
|
|
string_table_init(&new_graph->graph_string_table);
|
|
name = string_table_insert(&new_graph->graph_string_table, graph_attributes->ga_name);
|
|
|
|
/* Now fill in fields of graph structure */
|
|
new_graph->graph_name = name;
|
|
new_graph->graph_generation = 0;
|
|
new_graph->graph_separator_char = graph_attributes->ga_separator;
|
|
new_graph->graph_num_group = graph_num_group;
|
|
num_idx = new_graph->graph_num_index = graph_attributes->ga_num_index;
|
|
new_graph->graph_reserved_places = graph_attributes->ga_reserved_places;
|
|
|
|
MRLOCK_INIT(&new_graph->graph_mrlock, "hwg_root", -1);
|
|
|
|
new_graph->graph_num_vertex = 1; /* burn handle 0 */
|
|
new_graph->graph_vertex_handle_limit = GRAPH_VERTEX_PER_GROUP;
|
|
|
|
new_graph->graph_vertex_size =
|
|
sizeof(graph_vertex_t) + num_idx * sizeof(arbitrary_info_t);
|
|
|
|
/* Allocate space for group 0. */
|
|
new_vertex_array = graph_allocate(SIZE_GROUP_VERTEX_ARRAY(new_graph));
|
|
if (new_vertex_array == NULL) {
|
|
graph_free(new_graph);
|
|
return(GRAPH_CANNOT_ALLOC);
|
|
}
|
|
|
|
/*
|
|
* We count on the zero'ed allocation above to zero the v_info_list
|
|
* field of each vertex, and we count on such a zero value to
|
|
* indicate that the vertex is free.
|
|
*/
|
|
ASSERT((long)GRAPH_VERTEX_FREE == 0L); /* check just to catch dumb mistakes */
|
|
|
|
vertex_group = &(new_graph->graph_vertex_group[0]);
|
|
MRLOCK_INIT(&vertex_group->group_mrlock, "hwg_vgroup", 0);
|
|
|
|
/* Prevent use of the vertex with handle 0 (GRAPH_VERTEX_NONE) */
|
|
new_vertex_array[0].v_info_list = GRAPH_VERTEX_NEW;
|
|
vertex_group->group_count = 1;
|
|
vertex_group->group_vertex_array = new_vertex_array; /* Allocated above */
|
|
|
|
|
|
for (groupid=1; groupid<graph_num_group; groupid++) {
|
|
vertex_group = &(new_graph->graph_vertex_group[groupid]);
|
|
MRLOCK_INIT(&vertex_group->group_mrlock, "hwg_vgroup", groupid);
|
|
vertex_group->group_count = 0;
|
|
vertex_group->group_vertex_array = NULL;
|
|
}
|
|
|
|
new_graph->graph_group_rotor = 0;
|
|
|
|
*graph = new_graph;
|
|
|
|
if (current_graph == NULL)
|
|
current_graph = new_graph;
|
|
|
|
return(GRAPH_SUCCESS);
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
** graph_destroy frees all memory associated with a graph. After calling this
|
|
** routine, it is illegal to reference this graph.
|
|
**
|
|
** The caller must insure that there is currently no one referencing
|
|
** this graph and that no one will reference it in the future.
|
|
*/
|
|
graph_error_t
|
|
graph_destroy(graph_t *graph)
|
|
{
|
|
graph_vertex_group_t *vertex_group;
|
|
int groupid;
|
|
unsigned int graph_num_group;
|
|
|
|
if (graph == NULL)
|
|
return(GRAPH_BAD_PARAM);
|
|
|
|
if (graph->graph_num_vertex != 1) /* Allow unused handle 0 */
|
|
return(GRAPH_ILLEGAL_REQUEST);
|
|
|
|
graph_num_group = graph->graph_num_group;
|
|
|
|
for (groupid=0; groupid<graph_num_group; groupid++) {
|
|
vertex_group = &graph->graph_vertex_group[groupid];
|
|
MRLOCK_FREE(&vertex_group->group_mrlock);
|
|
if (vertex_group->group_vertex_array != NULL)
|
|
graph_free(vertex_group->group_vertex_array);
|
|
}
|
|
|
|
MRLOCK_FREE(&graph->graph_mrlock);
|
|
string_table_destroy(&graph->graph_string_table);
|
|
|
|
graph_free(graph);
|
|
|
|
return(GRAPH_SUCCESS);
|
|
}
|
|
|
|
|
|
/*
|
|
** graph_summary_get returns information about a graph, including attributes
|
|
** that were specified when the graph was created.
|
|
**
|
|
** No locking required.
|
|
*/
|
|
graph_error_t
|
|
graph_summary_get(graph_t *graph, graph_attr_t *infoptr)
|
|
{
|
|
if (graph == NULL)
|
|
return(GRAPH_BAD_PARAM);
|
|
|
|
if (infoptr == NULL)
|
|
return(GRAPH_BAD_PARAM);
|
|
|
|
infoptr->ga_name = graph->graph_name;
|
|
infoptr->ga_generation = graph->graph_generation;
|
|
infoptr->ga_separator = graph->graph_separator_char;
|
|
infoptr->ga_num_index = graph->graph_num_index;
|
|
infoptr->ga_reserved_places = graph->graph_reserved_places;
|
|
infoptr->ga_num_vertex = graph->graph_num_vertex;
|
|
infoptr->ga_num_vertex_max = graph->graph_num_group*GRAPH_VERTEX_PER_GROUP;
|
|
|
|
return(GRAPH_SUCCESS);
|
|
}
|
|
|
|
/*
|
|
** graph_vertex_visit executes an arbitrary function on every vertex in the graph
|
|
** If the vertex_visit_fn returns non-zero, traversal is halted. The vertex_visit
|
|
** function takes two arguments. The first arg is passed through from the call to
|
|
** graph_vertex_visit. The second arg is a vertex handle (representing the vertex
|
|
** being visited).
|
|
**
|
|
** This routine grabs a reference to the vertex before calling the specified function
|
|
** and it unreferences the vertex after return from the function.
|
|
*/
|
|
graph_error_t
|
|
graph_vertex_visit( graph_t *graph,
|
|
int (*vertex_visit_fn)(void *, vertex_hdl_t),
|
|
void *arg,
|
|
int *retvalp,
|
|
vertex_hdl_t *end_vertex_handle)
|
|
{
|
|
vertex_hdl_t next_vertex_handle;
|
|
int rv;
|
|
graph_error_t status;
|
|
graph_vertex_place_t place = GRAPH_VERTEX_PLACE_NONE;
|
|
|
|
if (graph == NULL)
|
|
return(GRAPH_BAD_PARAM);
|
|
|
|
while ( (status = graph_vertex_get_next(graph, &next_vertex_handle,
|
|
&place)) == GRAPH_SUCCESS) {
|
|
|
|
if (rv = vertex_visit_fn(arg, next_vertex_handle)) {
|
|
if (retvalp != NULL)
|
|
*retvalp = rv;
|
|
|
|
if (end_vertex_handle != NULL)
|
|
*end_vertex_handle = next_vertex_handle;
|
|
|
|
graph_vertex_unref(graph, next_vertex_handle);
|
|
|
|
return(GRAPH_SUCCESS);
|
|
}
|
|
}
|
|
|
|
ASSERT(status == GRAPH_HIT_LIMIT);
|
|
return(status);
|
|
}
|
|
|
|
|
|
|
|
|
|
/*********************************************************************************/
|
|
/* Operations on VERTICES: create, destroy, combine, clone, get_next, ref, unref */
|
|
/*********************************************************************************/
|
|
|
|
|
|
/*
|
|
** graph_vertex_create instantiates a vertex in the specified graph. Initially,
|
|
** the vertex is disconnected from the rest of the graph.
|
|
*/
|
|
graph_error_t
|
|
graph_vertex_create( graph_t *graph,
|
|
vertex_hdl_t *new_vertex_handle)
|
|
{
|
|
graph_error_t status;
|
|
vertex_hdl_t vertex_handle;
|
|
graph_vertex_t *vertex;
|
|
|
|
if (graph == NULL)
|
|
return(GRAPH_BAD_PARAM);
|
|
|
|
if (new_vertex_handle == NULL)
|
|
return(GRAPH_BAD_PARAM);
|
|
|
|
/*
|
|
* Find a handle for a new vertex.
|
|
*/
|
|
status = graph_vertex_allocate(graph, &vertex_handle, &vertex);
|
|
if (status != GRAPH_SUCCESS)
|
|
return(status);
|
|
|
|
vertex->v_info_list = GRAPH_VERTEX_NOINFO;
|
|
|
|
*new_vertex_handle = vertex_handle;
|
|
|
|
return(GRAPH_SUCCESS);
|
|
}
|
|
|
|
#if DEBUG_REFCT
|
|
int
|
|
graph_vertex_refct( graph_t *graph,
|
|
vertex_hdl_t vhdl)
|
|
{
|
|
graph_vertex_t *vertex;
|
|
graph_error_t status;
|
|
graph_vertex_group_t *vertex_group;
|
|
int junk_groupid;
|
|
int junk_grpidx;
|
|
|
|
if (graph == NULL)
|
|
return -1;
|
|
|
|
status = graph_vhandle_get_vertex(graph, vhdl, &vertex,
|
|
&vertex_group, &junk_groupid, &junk_grpidx);
|
|
|
|
if (status != GRAPH_SUCCESS)
|
|
return -1;
|
|
|
|
return vertex->v_refcnt;
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
** graph_vertex_destroy is used in conjunction with graph_vertex_create. It
|
|
** indicates that the caller no longer has any use for this vertex and would
|
|
** like to destroy it. If there are outstanding references to this vertex
|
|
** other than the original "creation reference", the destroy request is rejeted.
|
|
**
|
|
** Returns GRAPH_SUCCESS if the vertex was actually destroyed.
|
|
** GRAPH_IN_USE if the vertex still has references to it.
|
|
*/
|
|
graph_error_t
|
|
graph_vertex_destroy( graph_t *graph,
|
|
vertex_hdl_t vhdl)
|
|
{
|
|
graph_vertex_t *vertex;
|
|
graph_error_t status;
|
|
graph_vertex_group_t *vertex_group;
|
|
int junk_groupid;
|
|
int junk_grpidx;
|
|
int refcnt;
|
|
int s;
|
|
|
|
if (graph == NULL)
|
|
return(GRAPH_BAD_PARAM);
|
|
|
|
status = graph_vhandle_get_vertex(graph, vhdl, &vertex,
|
|
&vertex_group, &junk_groupid, &junk_grpidx);
|
|
|
|
if (status != GRAPH_SUCCESS)
|
|
return(status);
|
|
|
|
MRLOCK_UPDATE(&vertex_group->group_mrlock);
|
|
refcnt = vertex->v_refcnt;
|
|
ASSERT(refcnt >= 1);
|
|
|
|
if (refcnt == 1) {
|
|
(void)VERTEX_UNREF(vhdl, vertex);
|
|
graph_vertex_free(graph, vhdl);
|
|
MRLOCK_DONE_UPDATE(&vertex_group->group_mrlock);
|
|
return(GRAPH_SUCCESS);
|
|
} else
|
|
vertex->v_refcnt--;
|
|
|
|
MRLOCK_DONE_UPDATE(&vertex_group->group_mrlock);
|
|
|
|
return(GRAPH_IN_USE);
|
|
}
|
|
|
|
|
|
#if NOTDEF
|
|
/* Not yet implemented, since we don't currently have a use. */
|
|
|
|
/*
|
|
** graph_vertex_combine combines all vertex information from vertex2 to vertex1:
|
|
** For each edge that points to vertex2, add a corresponding edge that points
|
|
** to vertex1. For each edge that points out from vertex2, add a corresponding edge
|
|
** that points out from vertex1. When combining, silently ignore any edges or
|
|
** labelled information from vertex2 if the label is already in use by vertex1.
|
|
** Leave all indexed information for vertex1 unchanged. It is an error to combine
|
|
** a vertex with itself.
|
|
*/
|
|
graph_error_t
|
|
graph_vertex_combine( graph_t *graph,
|
|
vertex_hdl_t vertex1_handle,
|
|
vertex_hdl_t vertex2_handle)
|
|
{
|
|
}
|
|
#endif /* NOTDEF */
|
|
|
|
|
|
/*
|
|
** graph_vertex_clone creates a new vertex with all the same information and edges
|
|
** as the old vertex. The caller must hold a reference to the source vertex, so
|
|
** the source is guaranteed not to disappear while we're cloning.
|
|
*/
|
|
graph_error_t
|
|
graph_vertex_clone( graph_t *graph,
|
|
vertex_hdl_t old_vertex_handle,
|
|
vertex_hdl_t *new_vertex_handle)
|
|
{
|
|
graph_error_t status;
|
|
vertex_hdl_t new_vhandle;
|
|
graph_vertex_t *old_vertex, *new_vertex;
|
|
graph_vertex_group_t *old_vertex_group;
|
|
int old_groupid, old_grpidx;
|
|
int num_edge, num_info_LBL, num_info_IDX;
|
|
int new_vinfo_size, save_vinfo_size = 0;
|
|
void *old_list, *new_list = GRAPH_VERTEX_NOINFO;
|
|
arbitrary_info_t *old_info_IDX_list, *new_info_IDX_list;
|
|
int s;
|
|
|
|
if (new_vertex_handle == NULL)
|
|
return(GRAPH_BAD_PARAM);
|
|
|
|
/*
|
|
* Use vertex handle to find source vertex.
|
|
*/
|
|
status = graph_vhandle_get_vertex(graph, old_vertex_handle, &old_vertex,
|
|
&old_vertex_group, &old_groupid, &old_grpidx);
|
|
if (status != GRAPH_SUCCESS)
|
|
return(status);
|
|
|
|
/*
|
|
* Grab a new vertex for the clone.
|
|
*/
|
|
status = graph_vertex_allocate(graph, &new_vhandle, &new_vertex);
|
|
if (status != GRAPH_SUCCESS)
|
|
return(status);
|
|
|
|
num_info_IDX = graph->graph_num_index;
|
|
new_info_IDX_list = VERTEX_INFO_IDX_PTR(new_vertex);
|
|
old_info_IDX_list = VERTEX_INFO_IDX_PTR(old_vertex);
|
|
|
|
MRLOCK_ACCESS(&old_vertex_group->group_mrlock);
|
|
again:
|
|
num_edge = old_vertex->v_num_edge;
|
|
num_info_LBL = old_vertex->v_num_info_LBL;
|
|
|
|
new_vinfo_size = VERTEX_INFO_SIZE(num_edge, num_info_LBL);
|
|
|
|
/*
|
|
* If we haven't already allocated an info area, or if the one we
|
|
* allocated last time through this section isn't the right size,
|
|
* we need to allocate a new one.
|
|
*/
|
|
if (new_vinfo_size != save_vinfo_size) {
|
|
unsigned long old_generation = graph->graph_generation;
|
|
|
|
MRLOCK_DONE_ACCESS(&old_vertex_group->group_mrlock);
|
|
|
|
/*
|
|
* If we allocated one earlier, it's the wrong size. Free it.
|
|
*/
|
|
if (save_vinfo_size)
|
|
graph_free(new_list);
|
|
|
|
/*
|
|
* Create a new info area.
|
|
*/
|
|
save_vinfo_size = new_vinfo_size;
|
|
if (new_vinfo_size != 0) {
|
|
new_list = graph_allocate(new_vinfo_size);
|
|
if (new_list == NULL) {
|
|
(void)graph_vertex_free(graph, new_vhandle);
|
|
return(GRAPH_CANNOT_ALLOC);
|
|
}
|
|
} else
|
|
new_list = GRAPH_VERTEX_NOINFO;
|
|
|
|
MRLOCK_ACCESS(&old_vertex_group->group_mrlock);
|
|
|
|
/*
|
|
* If graph changed while we waited to allocate, start again.
|
|
*/
|
|
if (old_generation != graph->graph_generation)
|
|
goto again;
|
|
}
|
|
|
|
/*
|
|
* At this point, we are committed to cloning the vertex.
|
|
*/
|
|
old_list = old_vertex->v_info_list;
|
|
new_vertex->v_num_edge = num_edge;
|
|
new_vertex->v_num_info_LBL = num_info_LBL;
|
|
|
|
/*
|
|
* Copy old LBL and edge vertex info to new vertex
|
|
*/
|
|
bcopy(old_list, new_list, new_vinfo_size);
|
|
|
|
/*
|
|
* Copy indexed information.
|
|
*/
|
|
bcopy(old_info_IDX_list, new_info_IDX_list, num_info_IDX*sizeof(arbitrary_info_t));
|
|
|
|
/*
|
|
* Finally, make the new vertex visible.
|
|
*/
|
|
new_vertex->v_info_list = new_list;
|
|
|
|
MRLOCK_DONE_ACCESS(&old_vertex_group->group_mrlock);
|
|
*new_vertex_handle = new_vhandle;
|
|
return(GRAPH_SUCCESS);
|
|
}
|
|
|
|
|
|
/*
|
|
** graph_vertex_get_next is used to walk the entire list of vertices.
|
|
**
|
|
** It's possible for vertices to be added after we've already skipped over them.
|
|
** The caller must deal with this (possibly be using the graph generation number
|
|
** information).
|
|
**
|
|
** Along the way, vertices are automatically referenced and unreferenced. If the
|
|
** caller needs to retain a reference to a vertex obtained through get_next, he
|
|
** should use graph_vertex_ref explicitly.
|
|
**
|
|
** When there are no more vertex handles to return, get_next returns GRAPH_HIT_LIMIT.
|
|
*/
|
|
graph_error_t
|
|
graph_vertex_get_next( graph_t *graph,
|
|
vertex_hdl_t *vertex_found,
|
|
graph_vertex_place_t *placeptr)
|
|
{
|
|
int groupid, grpidx, junk_groupid, junk_grpidx;
|
|
unsigned int graph_num_group;
|
|
graph_vertex_t *vertex;
|
|
graph_vertex_group_t *vertex_group, *junk_vertex_group;
|
|
graph_error_t status;
|
|
vertex_hdl_t vertex_handle, old_vertex_handle;
|
|
int s;
|
|
|
|
if (graph == NULL)
|
|
return(GRAPH_BAD_PARAM);
|
|
|
|
if (vertex_found == NULL)
|
|
return(GRAPH_BAD_PARAM);
|
|
|
|
old_vertex_handle = (vertex_hdl_t)*placeptr;
|
|
|
|
if (old_vertex_handle != GRAPH_VERTEX_PLACE_NONE) {
|
|
graph_vertex_unref(graph, old_vertex_handle);
|
|
vertex_handle = old_vertex_handle+1; /* Advance to next vertex handle */
|
|
} else
|
|
vertex_handle = 1;
|
|
|
|
groupid = HANDLE_TO_GROUPID(graph, vertex_handle);
|
|
grpidx = HANDLE_TO_GRPIDX(graph, vertex_handle);
|
|
|
|
graph_num_group = graph->graph_num_group;
|
|
for (; groupid < graph_num_group; groupid++) {
|
|
vertex_group = GROUPID_TO_GROUP(graph, groupid);
|
|
MRLOCK_ACCESS(&vertex_group->group_mrlock);
|
|
vertex_handle = GRPIDX_TO_HANDLE(groupid, grpidx);
|
|
if (GROUPID_TO_GROUP(graph, groupid)->group_count != 0) {
|
|
for (; grpidx < GRAPH_VERTEX_PER_GROUP; grpidx++, vertex_handle++) {
|
|
|
|
status = graph_vhandle_get_vertex(graph, vertex_handle, &vertex,
|
|
&junk_vertex_group, &junk_groupid, &junk_grpidx);
|
|
|
|
if (status == GRAPH_SUCCESS) {
|
|
VERTEX_REF(vertex_handle, vertex);
|
|
MRLOCK_DONE_ACCESS(&vertex_group->group_mrlock);
|
|
*vertex_found = vertex_handle;
|
|
*placeptr = (graph_vertex_place_t)vertex_handle;
|
|
return(GRAPH_SUCCESS);
|
|
}
|
|
|
|
if (status == GRAPH_HIT_LIMIT) {
|
|
MRLOCK_DONE_ACCESS(&vertex_group->group_mrlock);
|
|
return(GRAPH_HIT_LIMIT);
|
|
}
|
|
}
|
|
}
|
|
MRLOCK_DONE_ACCESS(&vertex_group->group_mrlock);
|
|
grpidx = 0;
|
|
}
|
|
|
|
return(GRAPH_HIT_LIMIT);
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
** graph_vertex_refcnt reports back the reference count of
|
|
** a specified vertex, or "-999" if there was a problem.
|
|
*/
|
|
int
|
|
graph_vertex_refcnt(graph_t *graph, vertex_hdl_t vhdl)
|
|
{
|
|
graph_vertex_t *vertex;
|
|
graph_error_t status;
|
|
graph_vertex_group_t *vertex_group;
|
|
int junk_groupid;
|
|
int junk_grpidx;
|
|
|
|
if (graph == NULL)
|
|
return -999;
|
|
|
|
status = graph_vhandle_get_vertex(graph, vhdl, &vertex,
|
|
&vertex_group, &junk_groupid, &junk_grpidx);
|
|
|
|
if (status != GRAPH_SUCCESS)
|
|
return -999;
|
|
|
|
return vertex->v_refcnt;
|
|
}
|
|
|
|
|
|
/*
|
|
** graph_vertex_ref is used to indicate that there is an additional
|
|
** reference to the specified vertex (increments a reference count).
|
|
** Caller should graph_vertex_unref the vertex when he's done with it.
|
|
*/
|
|
graph_error_t
|
|
graph_vertex_ref(graph_t *graph, vertex_hdl_t vhdl)
|
|
{
|
|
graph_vertex_t *vertex;
|
|
graph_error_t status;
|
|
graph_vertex_group_t *vertex_group;
|
|
int junk_groupid;
|
|
int junk_grpidx;
|
|
|
|
if (graph == NULL)
|
|
return(GRAPH_BAD_PARAM);
|
|
|
|
status = graph_vhandle_get_vertex(graph, vhdl, &vertex,
|
|
&vertex_group, &junk_groupid, &junk_grpidx);
|
|
|
|
if (status != GRAPH_SUCCESS)
|
|
return(status);
|
|
|
|
ASSERT(vertex->v_refcnt > 0);
|
|
VERTEX_REF(vhdl, vertex);
|
|
|
|
return(GRAPH_SUCCESS);
|
|
}
|
|
|
|
|
|
/*
|
|
** graph_vertex_unref is used to indicate that a vertex is no longer
|
|
** in use by a particular user (decrements a reference count). When
|
|
** the vertex is no longer in use by anyone, it is freed.
|
|
**
|
|
** Returns GRAPH_SUCCESS if the vertex was actually destroyed.
|
|
** GRAPH_IN_USE if the vertex still has references to it.
|
|
*/
|
|
graph_error_t
|
|
graph_vertex_unref(graph_t *graph, vertex_hdl_t vhdl)
|
|
{
|
|
graph_vertex_t *vertex;
|
|
graph_error_t status;
|
|
graph_vertex_group_t *vertex_group;
|
|
int junk_groupid;
|
|
int junk_grpidx;
|
|
int refcnt;
|
|
int s;
|
|
|
|
if (graph == NULL)
|
|
return(GRAPH_BAD_PARAM);
|
|
|
|
status = graph_vhandle_get_vertex(graph, vhdl, &vertex,
|
|
&vertex_group, &junk_groupid, &junk_grpidx);
|
|
|
|
if (status != GRAPH_SUCCESS)
|
|
return(status);
|
|
|
|
refcnt = VERTEX_UNREF(vhdl, vertex);
|
|
ASSERT(refcnt >= 0);
|
|
if (refcnt == 0) {
|
|
MRLOCK_UPDATE(&vertex_group->group_mrlock);
|
|
graph_vertex_free(graph, vhdl);
|
|
MRLOCK_DONE_UPDATE(&vertex_group->group_mrlock);
|
|
}
|
|
|
|
return(GRAPH_SUCCESS);
|
|
}
|
|
|
|
|
|
/**********************************************/
|
|
/* Operations on EDGES: add, remove, get_next */
|
|
/**********************************************/
|
|
|
|
/*
|
|
** graph_edge_add adds a labelled edge from source_vertex to target_vertex.
|
|
*/
|
|
graph_error_t
|
|
graph_edge_add( graph_t *graph,
|
|
vertex_hdl_t source_vertex_handle,
|
|
vertex_hdl_t target_vertex_handle,
|
|
char *edge_name)
|
|
{
|
|
graph_error_t status;
|
|
graph_vertex_t *source_vertex, *target_vertex;
|
|
graph_vertex_group_t *source_vertex_group, *target_vertex_group;
|
|
int source_groupid, source_grpidx, target_groupid, target_grpidx;
|
|
int num_edge, num_info_LBL;
|
|
int new_vinfo_size, save_vinfo_size = 0;
|
|
graph_info_t *old_info_LBL, *new_info_LBL;
|
|
graph_edge_t *old_edges, *new_edges;
|
|
void *info_to_free, *new_list = GRAPH_VERTEX_NOINFO;
|
|
char *name;
|
|
int i;
|
|
int s;
|
|
|
|
if (graph == NULL)
|
|
return(GRAPH_BAD_PARAM);
|
|
|
|
if (edge_name == NULL)
|
|
return(GRAPH_BAD_PARAM);
|
|
|
|
if (strlen(edge_name) >= LABEL_LENGTH_MAX)
|
|
return(GRAPH_BAD_PARAM);
|
|
|
|
name = string_table_insert(&graph->graph_string_table, edge_name);
|
|
ASSERT(name != NULL);
|
|
|
|
/*
|
|
* Use vertex handle to find target vertex.
|
|
*/
|
|
status = graph_vhandle_get_vertex(graph, target_vertex_handle, &target_vertex,
|
|
&target_vertex_group, &target_groupid, &target_grpidx);
|
|
if (status != GRAPH_SUCCESS)
|
|
return(status);
|
|
|
|
/*
|
|
* Use vertex handle to find source vertex.
|
|
*/
|
|
status = graph_vhandle_get_vertex(graph, source_vertex_handle, &source_vertex,
|
|
&source_vertex_group, &source_groupid, &source_grpidx);
|
|
if (status != GRAPH_SUCCESS)
|
|
return(status);
|
|
|
|
MRLOCK_UPDATE(&source_vertex_group->group_mrlock);
|
|
again:
|
|
num_edge = source_vertex->v_num_edge;
|
|
num_info_LBL = source_vertex->v_num_info_LBL;
|
|
|
|
new_vinfo_size = VERTEX_INFO_SIZE(num_edge+1, num_info_LBL);
|
|
|
|
/*
|
|
* If we haven't already allocated an info area, or if the one we
|
|
* allocated last time through this section isn't the right size,
|
|
* we need to allocate a new one.
|
|
*/
|
|
if (new_vinfo_size != save_vinfo_size) {
|
|
unsigned long old_generation = graph->graph_generation;
|
|
|
|
MRLOCK_DONE_UPDATE(&source_vertex_group->group_mrlock);
|
|
|
|
/*
|
|
* If we allocated one earlier, it's the wrong size. Free it.
|
|
*/
|
|
if (save_vinfo_size)
|
|
graph_free(new_list);
|
|
|
|
/*
|
|
* Create a new info area.
|
|
*/
|
|
save_vinfo_size = new_vinfo_size;
|
|
if (new_vinfo_size != 0) {
|
|
new_list = graph_allocate(new_vinfo_size);
|
|
if (new_list == NULL)
|
|
return(GRAPH_CANNOT_ALLOC);
|
|
} else
|
|
new_list = GRAPH_VERTEX_NOINFO;
|
|
|
|
MRLOCK_UPDATE(&source_vertex_group->group_mrlock);
|
|
|
|
/*
|
|
* If graph changed while we waited to allocate, start again.
|
|
*/
|
|
if (old_generation != graph->graph_generation)
|
|
goto again;
|
|
}
|
|
|
|
/*
|
|
* At this point, we are committed to adding the edge, if there isn't
|
|
* already one there with the same name.
|
|
*/
|
|
old_edges = VERTEX_EDGE_PTR(source_vertex);
|
|
new_edges = EDGE_PTR(new_list, num_edge+1, num_info_LBL);
|
|
|
|
/*
|
|
* Look for matching edge name.
|
|
*/
|
|
for (i=0; i<num_edge; i++) {
|
|
if (!strcmp(edge_name, old_edges[i].e_label)) {
|
|
/* Not allowed to add duplicate edge names. */
|
|
MRLOCK_DONE_UPDATE(&source_vertex_group->group_mrlock);
|
|
graph_free(new_list);
|
|
return(GRAPH_DUP);
|
|
}
|
|
new_edges[i] = old_edges[i]; /* structure copy */
|
|
}
|
|
|
|
if (num_info_LBL != 0) {
|
|
old_info_LBL = VERTEX_INFO_LBL_PTR(source_vertex);
|
|
new_info_LBL = INFO_LBL_PTR(new_list, num_edge+1, num_info_LBL);
|
|
|
|
bcopy(old_info_LBL, new_info_LBL, num_info_LBL * sizeof(graph_info_t));
|
|
}
|
|
|
|
new_edges[num_edge].e_label = name;
|
|
new_edges[num_edge].e_vertex = target_vertex_handle;
|
|
|
|
(void)VERTEX_REF(target_vertex_handle, target_vertex);
|
|
|
|
if (VERTEX_HASINFO(source_vertex))
|
|
info_to_free = source_vertex->v_info_list;
|
|
else
|
|
info_to_free = NULL;
|
|
|
|
source_vertex->v_info_list = new_list;
|
|
source_vertex->v_num_edge = num_edge+1;
|
|
|
|
atomicAddUlong(&graph->graph_generation, 1);
|
|
MRLOCK_DONE_UPDATE(&source_vertex_group->group_mrlock);
|
|
|
|
if (info_to_free != NULL)
|
|
graph_free(info_to_free);
|
|
|
|
return(GRAPH_SUCCESS);
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
** graph_edge_remove removes the edge labelled edge_name that starts at source_vertex.
|
|
** If non-NULL, update target_vertex with the vertex that the edge pointed to.
|
|
*/
|
|
graph_error_t
|
|
graph_edge_remove( graph_t *graph,
|
|
vertex_hdl_t source_vertex_handle,
|
|
char *edge_name,
|
|
vertex_hdl_t *target_vertex_handle)
|
|
{
|
|
graph_error_t status;
|
|
vertex_hdl_t target_handle;
|
|
graph_vertex_t *source_vertex;
|
|
graph_vertex_group_t *source_vertex_group;
|
|
int source_groupid, source_grpidx;
|
|
int num_edge, num_info_LBL;
|
|
int new_vinfo_size, save_vinfo_size = 0;
|
|
graph_info_t *old_info_LBL, *new_info_LBL;
|
|
graph_edge_t *old_edges, *new_edges;
|
|
void *info_to_free, *new_list = GRAPH_VERTEX_NOINFO;
|
|
int i;
|
|
int s;
|
|
|
|
if (graph == NULL)
|
|
return(GRAPH_BAD_PARAM);
|
|
|
|
if (edge_name == NULL)
|
|
return(GRAPH_BAD_PARAM);
|
|
|
|
/*
|
|
* Use vertex handle to find source vertex.
|
|
*/
|
|
status = graph_vhandle_get_vertex(graph, source_vertex_handle, &source_vertex,
|
|
&source_vertex_group, &source_groupid, &source_grpidx);
|
|
if (status != GRAPH_SUCCESS)
|
|
return(status);
|
|
|
|
MRLOCK_UPDATE(&source_vertex_group->group_mrlock);
|
|
again:
|
|
num_edge = source_vertex->v_num_edge;
|
|
if (num_edge == 0) {
|
|
/*
|
|
* If there aren't any edges, save ourselves the trouble of looking.
|
|
*/
|
|
MRLOCK_DONE_UPDATE(&source_vertex_group->group_mrlock);
|
|
return(GRAPH_NOT_FOUND);
|
|
}
|
|
num_info_LBL = source_vertex->v_num_info_LBL;
|
|
|
|
new_vinfo_size = VERTEX_INFO_SIZE(num_edge-1, num_info_LBL);
|
|
|
|
/*
|
|
* If we haven't already allocated an info area, or if the one we
|
|
* allocated last time through this section isn't the right size,
|
|
* we need to allocate a new one.
|
|
*/
|
|
if (new_vinfo_size != save_vinfo_size) {
|
|
unsigned long old_generation = graph->graph_generation;
|
|
|
|
MRLOCK_DONE_UPDATE(&source_vertex_group->group_mrlock);
|
|
|
|
/*
|
|
* If we allocated one earlier, it's the wrong size. Free it.
|
|
*/
|
|
if (save_vinfo_size)
|
|
graph_free(new_list);
|
|
|
|
/*
|
|
* Create a new info area.
|
|
*/
|
|
save_vinfo_size = new_vinfo_size;
|
|
if (new_vinfo_size != 0) {
|
|
new_list = graph_allocate(new_vinfo_size);
|
|
if (new_list == NULL)
|
|
return(GRAPH_CANNOT_ALLOC);
|
|
} else
|
|
new_list = GRAPH_VERTEX_NOINFO;
|
|
|
|
MRLOCK_UPDATE(&source_vertex_group->group_mrlock);
|
|
|
|
/*
|
|
* If graph changed while we waited to allocate, start again.
|
|
*/
|
|
if (old_generation != graph->graph_generation)
|
|
goto again;
|
|
}
|
|
|
|
/*
|
|
* At this point, we are committed to removing the edge, if it still exists.
|
|
*/
|
|
old_edges = VERTEX_EDGE_PTR(source_vertex);
|
|
new_edges = EDGE_PTR(new_list, num_edge-1, num_info_LBL);
|
|
|
|
/*
|
|
* Find matching edge name.
|
|
*/
|
|
for (i=0; i<num_edge; i++) {
|
|
if (!strcmp(edge_name, old_edges[i].e_label)) {
|
|
target_handle = old_edges[i].e_vertex;
|
|
goto found;
|
|
}
|
|
if (i < num_edge-1) /* avoid walking off the end of the new vertex */
|
|
new_edges[i] = old_edges[i]; /* structure copy */
|
|
}
|
|
|
|
/* The named edge doesn't exist. */
|
|
MRLOCK_DONE_UPDATE(&source_vertex_group->group_mrlock);
|
|
if (new_vinfo_size)
|
|
graph_free(new_list);
|
|
return(GRAPH_NOT_FOUND);
|
|
|
|
found:
|
|
/* Finish up rest of edges */
|
|
for (i=i+1; i<num_edge; i++)
|
|
new_edges[i-1] = old_edges[i]; /* structure copy */
|
|
|
|
/* Now copy labelled info */
|
|
if (num_info_LBL != 0) {
|
|
old_info_LBL = VERTEX_INFO_LBL_PTR(source_vertex);
|
|
new_info_LBL = INFO_LBL_PTR(new_list, num_edge-1, num_info_LBL);
|
|
bcopy(old_info_LBL, new_info_LBL, num_info_LBL * sizeof(graph_info_t));
|
|
}
|
|
|
|
info_to_free = source_vertex->v_info_list;
|
|
ASSERT(info_to_free != NULL);
|
|
|
|
source_vertex->v_info_list = new_list;
|
|
source_vertex->v_num_edge = num_edge-1;
|
|
|
|
atomicAddUlong(&graph->graph_generation, 1);
|
|
MRLOCK_DONE_UPDATE(&source_vertex_group->group_mrlock);
|
|
|
|
graph_free(info_to_free);
|
|
|
|
if (target_vertex_handle != NULL)
|
|
*target_vertex_handle = target_handle;
|
|
else
|
|
graph_vertex_unref(graph, target_handle);
|
|
|
|
return(GRAPH_SUCCESS);
|
|
}
|
|
|
|
|
|
/*
|
|
** graph_edge_get finds which vertex the specified edge points to.
|
|
*/
|
|
graph_error_t
|
|
graph_edge_get( graph_t *graph,
|
|
vertex_hdl_t vertex_handle,
|
|
char *edge_name,
|
|
vertex_hdl_t *target_handle)
|
|
{
|
|
graph_error_t status;
|
|
graph_vertex_t *vertex;
|
|
graph_vertex_group_t *vertex_group;
|
|
int groupid, grpidx;
|
|
graph_edge_t *edge_list;
|
|
int num_edge;
|
|
int i;
|
|
int s;
|
|
|
|
if (graph == NULL)
|
|
return(GRAPH_BAD_PARAM);
|
|
|
|
if (edge_name == NULL)
|
|
return(GRAPH_BAD_PARAM);
|
|
|
|
/*
|
|
* Use vertex handle to find vertex.
|
|
*/
|
|
status = graph_vhandle_get_vertex(graph, vertex_handle, &vertex,
|
|
&vertex_group, &groupid, &grpidx);
|
|
if (status != GRAPH_SUCCESS)
|
|
return(status);
|
|
|
|
MRLOCK_ACCESS(&vertex_group->group_mrlock);
|
|
|
|
edge_list = VERTEX_EDGE_PTR(vertex);
|
|
num_edge = vertex->v_num_edge;
|
|
|
|
/*
|
|
* See if edge under edge_name exists.
|
|
*/
|
|
for (i=0; i<num_edge; i++)
|
|
if (!strcmp(edge_name, edge_list[i].e_label)) {
|
|
if (target_handle != NULL) {
|
|
*target_handle = edge_list[i].e_vertex;
|
|
(void)graph_vertex_ref(graph, *target_handle);
|
|
}
|
|
|
|
MRLOCK_DONE_ACCESS(&vertex_group->group_mrlock);
|
|
return(GRAPH_SUCCESS);
|
|
}
|
|
|
|
MRLOCK_DONE_ACCESS(&vertex_group->group_mrlock);
|
|
return(GRAPH_NOT_FOUND);
|
|
}
|
|
|
|
|
|
/*
|
|
** graph_edge_get_next is used to walk all edges of a given vertex. On entry,
|
|
** if *placeptr is EDGE_PLACE_NONE, this routine returns the first edge in this
|
|
** vertex.
|
|
**
|
|
** This routine returns both the name of the edge and the vertex it targets.
|
|
** The caller holds a reference to the target vertex, so he must graph_vertex_unref
|
|
** the target when he's done with it.
|
|
*/
|
|
graph_error_t
|
|
graph_edge_get_next( graph_t *graph,
|
|
vertex_hdl_t vertex_handle,
|
|
char *buffer,
|
|
vertex_hdl_t *targetp,
|
|
graph_edge_place_t *placeptr)
|
|
{
|
|
graph_error_t status;
|
|
uint which_edge;
|
|
graph_vertex_t *vertex;
|
|
graph_vertex_group_t *vertex_group;
|
|
int groupid, grpidx;
|
|
graph_edge_t *edge_list;
|
|
int s;
|
|
|
|
if (graph == NULL)
|
|
return(GRAPH_BAD_PARAM);
|
|
|
|
if (placeptr == NULL)
|
|
return(GRAPH_BAD_PARAM);
|
|
|
|
if (*placeptr == GRAPH_EDGE_PLACE_NONE)
|
|
which_edge = 0;
|
|
else
|
|
which_edge = *placeptr - graph->graph_reserved_places;
|
|
|
|
status = graph_vhandle_get_vertex(graph, vertex_handle, &vertex,
|
|
&vertex_group, &groupid, &grpidx);
|
|
if (status != GRAPH_SUCCESS)
|
|
return(status);
|
|
|
|
MRLOCK_ACCESS(&vertex_group->group_mrlock);
|
|
|
|
if (which_edge >= vertex->v_num_edge) {
|
|
MRLOCK_DONE_ACCESS(&vertex_group->group_mrlock);
|
|
return(GRAPH_HIT_LIMIT);
|
|
}
|
|
|
|
edge_list = VERTEX_EDGE_PTR(vertex);
|
|
|
|
if (buffer)
|
|
strcpy(buffer, edge_list[which_edge].e_label);
|
|
|
|
if (targetp) {
|
|
*targetp = edge_list[which_edge].e_vertex;
|
|
(void)graph_vertex_ref(graph, *targetp);
|
|
}
|
|
|
|
MRLOCK_DONE_ACCESS(&vertex_group->group_mrlock);
|
|
|
|
*placeptr = which_edge + 1 + graph->graph_reserved_places;
|
|
|
|
return(GRAPH_SUCCESS);
|
|
}
|
|
|
|
|
|
|
|
/***************************************************************************/
|
|
/* Operations on LABELLED INFORMATION: add, remove, replace, get, get_next */
|
|
/***************************************************************************/
|
|
|
|
/*
|
|
** graph_info_add_LBL associates the specified arbitrary information with the
|
|
** specified vertex and information label.
|
|
*/
|
|
graph_error_t
|
|
graph_info_add_LBL( graph_t *graph,
|
|
vertex_hdl_t vertex_handle,
|
|
char *info_name,
|
|
arb_info_desc_t info_desc,
|
|
arbitrary_info_t info)
|
|
{
|
|
graph_error_t status;
|
|
graph_vertex_t *vertex;
|
|
graph_vertex_group_t *vertex_group;
|
|
int groupid, grpidx;
|
|
int num_edge, num_info_LBL;
|
|
int new_vinfo_size, save_vinfo_size = 0;
|
|
graph_info_t *old_info_LBL, *new_info_LBL;
|
|
graph_edge_t *old_edges, *new_edges;
|
|
void *info_to_free, *new_list = GRAPH_VERTEX_NOINFO;
|
|
char *name;
|
|
int i;
|
|
int s;
|
|
|
|
if (graph == NULL)
|
|
return(GRAPH_BAD_PARAM);
|
|
|
|
if (info_name == NULL)
|
|
return(GRAPH_BAD_PARAM);
|
|
|
|
if (strlen(info_name) >= LABEL_LENGTH_MAX)
|
|
return(GRAPH_BAD_PARAM);
|
|
|
|
name = string_table_insert(&graph->graph_string_table, info_name);
|
|
ASSERT(name != NULL);
|
|
|
|
/*
|
|
* Use vertex handle to find vertex.
|
|
*/
|
|
status = graph_vhandle_get_vertex(graph, vertex_handle, &vertex,
|
|
&vertex_group, &groupid, &grpidx);
|
|
if (status != GRAPH_SUCCESS)
|
|
return(status);
|
|
|
|
MRLOCK_UPDATE(&vertex_group->group_mrlock);
|
|
again:
|
|
num_edge = vertex->v_num_edge;
|
|
num_info_LBL = vertex->v_num_info_LBL;
|
|
|
|
new_vinfo_size = VERTEX_INFO_SIZE(num_edge, num_info_LBL+1);
|
|
|
|
/*
|
|
* If we haven't already allocated an info area, or if the one we
|
|
* allocated last time through this section isn't the right size,
|
|
* we need to allocate a new one.
|
|
*/
|
|
if (new_vinfo_size != save_vinfo_size) {
|
|
unsigned long old_generation = graph->graph_generation;
|
|
|
|
MRLOCK_DONE_UPDATE(&vertex_group->group_mrlock);
|
|
|
|
/*
|
|
* If we allocated one earlier, it's the wrong size. Free it.
|
|
*/
|
|
if (save_vinfo_size)
|
|
graph_free(new_list);
|
|
|
|
/*
|
|
* Create a new info area.
|
|
*/
|
|
save_vinfo_size = new_vinfo_size;
|
|
if (new_vinfo_size != 0) {
|
|
new_list = graph_allocate(new_vinfo_size);
|
|
if (new_list == NULL)
|
|
return(GRAPH_CANNOT_ALLOC);
|
|
} else
|
|
new_list = GRAPH_VERTEX_NOINFO;
|
|
|
|
MRLOCK_UPDATE(&vertex_group->group_mrlock);
|
|
|
|
/*
|
|
* If graph changed while we waited to allocate, start again.
|
|
*/
|
|
if (old_generation != graph->graph_generation)
|
|
goto again;
|
|
}
|
|
|
|
/*
|
|
* At this point, we are committed to adding the labelled info, if there
|
|
* isn't already information there with the same name.
|
|
*/
|
|
old_info_LBL = VERTEX_INFO_LBL_PTR(vertex);
|
|
new_info_LBL = INFO_LBL_PTR(new_list, num_edge, num_info_LBL+1);
|
|
|
|
/*
|
|
* Look for matching info name.
|
|
*/
|
|
for (i=0; i<num_info_LBL; i++) {
|
|
if (!strcmp(info_name, old_info_LBL[i].i_label)) {
|
|
/* Not allowed to add duplicate labelled info names. */
|
|
MRLOCK_DONE_UPDATE(&vertex_group->group_mrlock);
|
|
graph_free(new_list);
|
|
return(GRAPH_DUP);
|
|
}
|
|
new_info_LBL[i] = old_info_LBL[i]; /* structure copy */
|
|
}
|
|
|
|
if (num_edge != 0) {
|
|
old_edges = VERTEX_EDGE_PTR(vertex);
|
|
new_edges = EDGE_PTR(new_list, num_edge, num_info_LBL+1);
|
|
|
|
bcopy(old_edges, new_edges, num_edge * sizeof(graph_edge_t));
|
|
}
|
|
|
|
new_info_LBL[num_info_LBL].i_label = name;
|
|
new_info_LBL[num_info_LBL].i_info_desc = info_desc;
|
|
new_info_LBL[num_info_LBL].i_info = info;
|
|
|
|
if (VERTEX_HASINFO(vertex))
|
|
info_to_free = vertex->v_info_list;
|
|
else
|
|
info_to_free = NULL;
|
|
|
|
vertex->v_info_list = new_list;
|
|
vertex->v_num_info_LBL = num_info_LBL+1;
|
|
|
|
atomicAddUlong(&graph->graph_generation, 1);
|
|
MRLOCK_DONE_UPDATE(&vertex_group->group_mrlock);
|
|
|
|
if (info_to_free != NULL)
|
|
graph_free(info_to_free);
|
|
|
|
return(GRAPH_SUCCESS);
|
|
}
|
|
|
|
|
|
/*
|
|
** graph_info_remove_LBL disassociates the specified information from the
|
|
** vertex (specified as labelled information on a vertex). Return info that
|
|
** was referred to.
|
|
*/
|
|
graph_error_t
|
|
graph_info_remove_LBL( graph_t *graph,
|
|
vertex_hdl_t vertex_handle,
|
|
char *info_name,
|
|
arb_info_desc_t *info_desc,
|
|
arbitrary_info_t *info)
|
|
{
|
|
graph_error_t status;
|
|
graph_vertex_t *vertex;
|
|
graph_vertex_group_t *vertex_group;
|
|
int groupid, grpidx;
|
|
int num_edge, num_info_LBL;
|
|
int new_vinfo_size, save_vinfo_size = 0;
|
|
graph_info_t *new_list = GRAPH_VERTEX_NOINFO;
|
|
graph_info_t *old_info_LBL, *new_info_LBL;
|
|
graph_edge_t *old_edges, *new_edges;
|
|
graph_info_t *info_to_free;
|
|
arbitrary_info_t info_found;
|
|
arb_info_desc_t info_found_desc;
|
|
int i;
|
|
int s;
|
|
|
|
if (graph == NULL)
|
|
return(GRAPH_BAD_PARAM);
|
|
|
|
if (info_name == NULL)
|
|
return(GRAPH_BAD_PARAM);
|
|
|
|
/*
|
|
* Use vertex handle to find vertex.
|
|
*/
|
|
status = graph_vhandle_get_vertex(graph, vertex_handle, &vertex,
|
|
&vertex_group, &groupid, &grpidx);
|
|
if (status != GRAPH_SUCCESS)
|
|
return(status);
|
|
|
|
MRLOCK_UPDATE(&vertex_group->group_mrlock);
|
|
again:
|
|
num_info_LBL = vertex->v_num_info_LBL;
|
|
if (num_info_LBL == 0) {
|
|
/* If there's no LBL info, save ourselves the trouble of looking. */
|
|
MRLOCK_DONE_UPDATE(&vertex_group->group_mrlock);
|
|
return(GRAPH_NOT_FOUND);
|
|
}
|
|
num_edge = vertex->v_num_edge;
|
|
|
|
new_vinfo_size = VERTEX_INFO_SIZE(num_edge, num_info_LBL-1);
|
|
|
|
/*
|
|
* If we haven't already allocated an info area, or if the one we
|
|
* allocated last time through this section isn't the right size,
|
|
* we need to allocate a new one.
|
|
*/
|
|
if (new_vinfo_size != save_vinfo_size) {
|
|
unsigned long old_generation = graph->graph_generation;
|
|
|
|
MRLOCK_DONE_UPDATE(&vertex_group->group_mrlock);
|
|
|
|
/*
|
|
* If we allocated one earlier, it's the wrong size. Free it.
|
|
*/
|
|
if (save_vinfo_size)
|
|
graph_free(new_list);
|
|
|
|
/*
|
|
* Create a new info area.
|
|
*/
|
|
save_vinfo_size = new_vinfo_size;
|
|
if (new_vinfo_size != 0) {
|
|
new_list = graph_allocate(new_vinfo_size);
|
|
if (new_list == NULL)
|
|
return(GRAPH_CANNOT_ALLOC);
|
|
} else
|
|
new_list = GRAPH_VERTEX_NOINFO;
|
|
|
|
MRLOCK_UPDATE(&vertex_group->group_mrlock);
|
|
|
|
/*
|
|
* If graph changed while we waited to allocate, start again.
|
|
*/
|
|
if (old_generation != graph->graph_generation)
|
|
goto again;
|
|
}
|
|
|
|
/*
|
|
* At this point, we are committed to removing the labelled info,
|
|
* if it still exists.
|
|
*/
|
|
old_info_LBL = VERTEX_INFO_LBL_PTR(vertex);
|
|
new_info_LBL = INFO_LBL_PTR(new_list, num_edge, num_info_LBL-1);
|
|
|
|
/*
|
|
* Find matching info name.
|
|
*/
|
|
for (i=0; i<num_info_LBL; i++) {
|
|
if (!strcmp(info_name, old_info_LBL[i].i_label)) {
|
|
info_found_desc = old_info_LBL[i].i_info_desc;
|
|
info_found = old_info_LBL[i].i_info;
|
|
goto found;
|
|
}
|
|
if (i < num_info_LBL-1) /* avoid walking off the end of the new vertex */
|
|
new_info_LBL[i] = old_info_LBL[i]; /* structure copy */
|
|
}
|
|
|
|
/* The named info doesn't exist. */
|
|
MRLOCK_DONE_UPDATE(&vertex_group->group_mrlock);
|
|
if (new_vinfo_size)
|
|
graph_free(new_list);
|
|
return(GRAPH_NOT_FOUND);
|
|
|
|
found:
|
|
/* Finish up rest of labelled info */
|
|
for (i=i+1; i<num_info_LBL; i++)
|
|
new_info_LBL[i-1] = old_info_LBL[i]; /* structure copy */
|
|
|
|
/* Now copy edges */
|
|
if (num_edge != 0) {
|
|
old_edges = VERTEX_EDGE_PTR(vertex);
|
|
new_edges = EDGE_PTR(new_list, num_edge, num_info_LBL-1);
|
|
bcopy(old_edges, new_edges, num_edge * sizeof(graph_edge_t));
|
|
}
|
|
|
|
info_to_free = vertex->v_info_list;
|
|
ASSERT(info_to_free != NULL);
|
|
|
|
vertex->v_info_list = new_list;
|
|
vertex->v_num_info_LBL = num_info_LBL-1;
|
|
|
|
atomicAddUlong(&graph->graph_generation, 1);
|
|
MRLOCK_DONE_UPDATE(&vertex_group->group_mrlock);
|
|
|
|
graph_free(info_to_free);
|
|
|
|
if (info != NULL)
|
|
*info = info_found;
|
|
|
|
if (info_desc != NULL)
|
|
*info_desc = info_found_desc;
|
|
|
|
return(GRAPH_SUCCESS);
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
** graph_info_replace_LBL replaces the specified pointer-sized information at the
|
|
** specified vertex and information label with a new value. Note that in the case
|
|
** of labelled information, replacing with NULL is NOT the same as removing.
|
|
*/
|
|
graph_error_t
|
|
graph_info_replace_LBL( graph_t *graph,
|
|
vertex_hdl_t vertex_handle,
|
|
char *info_name,
|
|
arb_info_desc_t info_desc,
|
|
arbitrary_info_t info,
|
|
arb_info_desc_t *old_info_desc,
|
|
arbitrary_info_t *old_info)
|
|
{
|
|
graph_error_t status;
|
|
graph_vertex_t *vertex;
|
|
graph_vertex_group_t *vertex_group;
|
|
int groupid, grpidx;
|
|
unsigned int num_info_LBL;
|
|
graph_info_t *info_list_LBL;
|
|
int i;
|
|
int s;
|
|
|
|
if (graph == NULL)
|
|
return(GRAPH_BAD_PARAM);
|
|
|
|
if (info_name == NULL)
|
|
return(GRAPH_BAD_PARAM);
|
|
|
|
/*
|
|
* Use vertex handle to find vertex.
|
|
*/
|
|
status = graph_vhandle_get_vertex(graph, vertex_handle, &vertex,
|
|
&vertex_group, &groupid, &grpidx);
|
|
if (status != GRAPH_SUCCESS)
|
|
return(GRAPH_BAD_PARAM);
|
|
|
|
MRLOCK_UPDATE(&vertex_group->group_mrlock);
|
|
|
|
num_info_LBL = vertex->v_num_info_LBL;
|
|
info_list_LBL = VERTEX_INFO_LBL_PTR(vertex);
|
|
|
|
/*
|
|
* Verify that information under info_name already exists.
|
|
*/
|
|
for (i=0; i<num_info_LBL; i++)
|
|
if (!strcmp(info_name, info_list_LBL[i].i_label)) {
|
|
if (old_info != NULL)
|
|
*old_info = info_list_LBL[i].i_info;
|
|
|
|
if (old_info_desc != NULL)
|
|
*old_info_desc = info_list_LBL[i].i_info_desc;
|
|
|
|
info_list_LBL[i].i_info = info;
|
|
info_list_LBL[i].i_info_desc = info_desc;
|
|
|
|
atomicAddUlong(&graph->graph_generation, 1);
|
|
MRLOCK_DONE_UPDATE(&vertex_group->group_mrlock);
|
|
return(GRAPH_SUCCESS);
|
|
}
|
|
|
|
MRLOCK_DONE_UPDATE(&vertex_group->group_mrlock);
|
|
|
|
return(GRAPH_NOT_FOUND);
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
** graph_info_get_LBL retrieves arbitrary labelled information from the specified
|
|
** vertex.
|
|
*/
|
|
graph_error_t
|
|
graph_info_get_LBL( graph_t *graph,
|
|
vertex_hdl_t vertex_handle,
|
|
char *info_name,
|
|
arb_info_desc_t *info_desc,
|
|
arbitrary_info_t *info)
|
|
{
|
|
graph_error_t status;
|
|
graph_vertex_t *vertex;
|
|
graph_vertex_group_t *vertex_group;
|
|
int groupid, grpidx;
|
|
unsigned int num_info_LBL;
|
|
graph_info_t *info_list_LBL;
|
|
int i;
|
|
int s;
|
|
|
|
if (graph == NULL)
|
|
return(GRAPH_BAD_PARAM);
|
|
|
|
if (info_name == NULL)
|
|
return(GRAPH_BAD_PARAM);
|
|
|
|
/*
|
|
* Use vertex handle to find vertex.
|
|
*/
|
|
status = graph_vhandle_get_vertex(graph, vertex_handle, &vertex,
|
|
&vertex_group, &groupid, &grpidx);
|
|
if (status != GRAPH_SUCCESS)
|
|
return(GRAPH_BAD_PARAM);
|
|
|
|
MRLOCK_ACCESS(&vertex_group->group_mrlock);
|
|
|
|
num_info_LBL = vertex->v_num_info_LBL;
|
|
info_list_LBL = VERTEX_INFO_LBL_PTR(vertex);
|
|
|
|
/*
|
|
* Find information under info_name.
|
|
*/
|
|
for (i=0; i<num_info_LBL; i++)
|
|
if (!strcmp(info_name, info_list_LBL[i].i_label)) {
|
|
if (info != NULL)
|
|
*info = info_list_LBL[i].i_info;
|
|
if (info_desc != NULL)
|
|
*info_desc = info_list_LBL[i].i_info_desc;
|
|
|
|
MRLOCK_DONE_ACCESS(&vertex_group->group_mrlock);
|
|
return(GRAPH_SUCCESS);
|
|
}
|
|
|
|
MRLOCK_DONE_ACCESS(&vertex_group->group_mrlock);
|
|
return(GRAPH_NOT_FOUND);
|
|
}
|
|
|
|
|
|
/*
|
|
** graph_info_get_next_LBL is used to walk arbitrary labelled information associated
|
|
** with a given vertex. On entry, if *placeptr is INFO_PLACE_NONE, this routine
|
|
** returns the first piece of labelled information in this vertex.
|
|
**
|
|
** This routine returns both the name of the labelled information and the
|
|
** information itself.
|
|
*/
|
|
graph_error_t
|
|
graph_info_get_next_LBL( graph_t *graph,
|
|
vertex_hdl_t vertex_handle,
|
|
char *buffer,
|
|
arb_info_desc_t *info_descp,
|
|
arbitrary_info_t *infop,
|
|
graph_info_place_t *placeptr)
|
|
{
|
|
uint which_info;
|
|
graph_error_t status;
|
|
graph_vertex_t *vertex;
|
|
graph_vertex_group_t *vertex_group;
|
|
int groupid, grpidx;
|
|
graph_info_t *info_list_LBL;
|
|
int s;
|
|
|
|
if (graph == NULL)
|
|
return(GRAPH_BAD_PARAM);
|
|
|
|
if ((buffer == NULL) && (infop == NULL))
|
|
return(GRAPH_BAD_PARAM);
|
|
|
|
if (placeptr == NULL)
|
|
return(GRAPH_BAD_PARAM);
|
|
|
|
if (*placeptr == GRAPH_INFO_PLACE_NONE)
|
|
which_info = 0;
|
|
else
|
|
which_info = *placeptr - graph->graph_reserved_places;
|
|
|
|
status = graph_vhandle_get_vertex(graph, vertex_handle, &vertex,
|
|
&vertex_group, &groupid, &grpidx);
|
|
if (status != GRAPH_SUCCESS)
|
|
return(status);
|
|
|
|
MRLOCK_ACCESS(&vertex_group->group_mrlock);
|
|
|
|
if (which_info >= vertex->v_num_info_LBL) {
|
|
MRLOCK_DONE_ACCESS(&vertex_group->group_mrlock);
|
|
return(GRAPH_HIT_LIMIT);
|
|
}
|
|
|
|
info_list_LBL = VERTEX_INFO_LBL_PTR(vertex);
|
|
|
|
if (buffer != NULL)
|
|
strcpy(buffer, info_list_LBL[which_info].i_label);
|
|
|
|
if (infop)
|
|
*infop = info_list_LBL[which_info].i_info;
|
|
|
|
if (info_descp)
|
|
*info_descp = info_list_LBL[which_info].i_info_desc;
|
|
|
|
MRLOCK_DONE_ACCESS(&vertex_group->group_mrlock);
|
|
|
|
*placeptr = which_info + 1 + graph->graph_reserved_places;
|
|
|
|
return(GRAPH_SUCCESS);
|
|
}
|
|
|
|
|
|
|
|
/***************************************************/
|
|
/* Operations on INDEXED INFORMATION: replace, get */
|
|
/***************************************************/
|
|
|
|
/*
|
|
** graph_info_replace_IDX associates the specified long-sized information with the
|
|
** specified vertex and index. The index must be less than the num_idx_info attribute
|
|
** specified to create_graph. Any previous information at the specified index is
|
|
** replaced.
|
|
**
|
|
** No locking is required since we're atomically changing a single longword.
|
|
**
|
|
** The graph generation number is NOT altered.
|
|
*/
|
|
graph_error_t
|
|
graph_info_replace_IDX( graph_t *graph,
|
|
vertex_hdl_t vertex_handle,
|
|
unsigned int index,
|
|
arbitrary_info_t info,
|
|
arbitrary_info_t *old_info)
|
|
{
|
|
graph_error_t status;
|
|
graph_vertex_t *vertex;
|
|
graph_vertex_group_t *vertex_group;
|
|
int groupid, grpidx;
|
|
arbitrary_info_t *info_list_IDX;
|
|
|
|
if (graph == NULL)
|
|
return(GRAPH_BAD_PARAM);
|
|
|
|
if (index >= graph->graph_num_index)
|
|
return(GRAPH_BAD_PARAM);
|
|
|
|
/*
|
|
* Use vertex handle to find vertex.
|
|
*/
|
|
status = graph_vhandle_get_vertex(graph, vertex_handle, &vertex,
|
|
&vertex_group, &groupid, &grpidx);
|
|
if (status != GRAPH_SUCCESS)
|
|
return(status);
|
|
|
|
/*
|
|
* Replace information at the appropriate index in this vertex with the new info.
|
|
*/
|
|
info_list_IDX = VERTEX_INFO_IDX_PTR(vertex);
|
|
if (old_info != NULL)
|
|
*old_info = info_list_IDX[index];
|
|
info_list_IDX[index] = info;
|
|
|
|
return(GRAPH_SUCCESS);
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
** graph_info_get_IDX retrieves arbitrary information from the specicified vertex and
|
|
** index and places it into the caller's buffer. The index must be less than the
|
|
** num_idx_info attribute specified to create_graph.
|
|
**
|
|
** No locking is required since we're atomically accessing a single longword.
|
|
*/
|
|
graph_error_t
|
|
graph_info_get_IDX( graph_t *graph,
|
|
vertex_hdl_t vertex_handle,
|
|
unsigned int index,
|
|
arbitrary_info_t *info)
|
|
{
|
|
graph_error_t status;
|
|
graph_vertex_t *vertex;
|
|
graph_vertex_group_t *vertex_group;
|
|
int groupid, grpidx;
|
|
arbitrary_info_t *info_list_IDX;
|
|
|
|
if (graph == NULL)
|
|
return(GRAPH_BAD_PARAM);
|
|
|
|
if (index >= graph->graph_num_index)
|
|
return(GRAPH_BAD_PARAM);
|
|
|
|
/*
|
|
* Use vertex handle to find vertex.
|
|
*/
|
|
status = graph_vhandle_get_vertex(graph, vertex_handle, &vertex,
|
|
&vertex_group, &groupid, &grpidx);
|
|
if (status != GRAPH_SUCCESS)
|
|
return(status);
|
|
|
|
/*
|
|
* Return information at the appropriate index in this vertex.
|
|
*/
|
|
info_list_IDX = VERTEX_INFO_IDX_PTR(vertex);
|
|
if (info != NULL)
|
|
*info = info_list_IDX[index];
|
|
|
|
return(GRAPH_SUCCESS);
|
|
}
|
|
|
|
|
|
/****************************************/
|
|
/* Operations on STRINGS: get_component */
|
|
/****************************************/
|
|
|
|
/*
|
|
** graph_path_get_component extracts the next component from lookup_path. Return
|
|
** the component and its length. Components in lookup_path are separated by the
|
|
** separator character specified to graph_create. The length returned is the number of
|
|
** real characters in the component (does NOT include separator or terminator). Also
|
|
** returned is the number of separator characters skipped over before a component was
|
|
** found.
|
|
*/
|
|
graph_error_t
|
|
graph_path_get_component( graph_t *graph,
|
|
char *lookup_path,
|
|
char *component,
|
|
int *separator_length,
|
|
int *component_length)
|
|
{
|
|
char separator;
|
|
int separator_count;
|
|
int i;
|
|
|
|
if (graph == NULL)
|
|
return(GRAPH_BAD_PARAM);
|
|
|
|
if (lookup_path == NULL)
|
|
return(GRAPH_BAD_PARAM);
|
|
|
|
if (component == NULL)
|
|
return(GRAPH_BAD_PARAM);
|
|
|
|
/*
|
|
* Skip over redundant separator characters.
|
|
*/
|
|
separator_count = 0;
|
|
separator = graph->graph_separator_char;
|
|
while (lookup_path[separator_count] == separator)
|
|
separator_count++;
|
|
lookup_path += separator_count;
|
|
|
|
/*
|
|
* Now, copy component to caller's buffer.
|
|
*/
|
|
for (i=0; i<LABEL_LENGTH_MAX; i++) {
|
|
if ((lookup_path[i] == '\0') ||
|
|
(lookup_path[i] == separator)) {
|
|
component[i] = 0;
|
|
break;
|
|
} else
|
|
component[i] = lookup_path[i];
|
|
}
|
|
|
|
if (i == LABEL_LENGTH_MAX)
|
|
return(GRAPH_HIT_LIMIT);
|
|
|
|
if (separator_length != NULL)
|
|
*separator_length = separator_count;
|
|
|
|
if (component_length != NULL)
|
|
*component_length = i;
|
|
|
|
return(GRAPH_SUCCESS);
|
|
}
|
|
|
|
#if DEBUG
|
|
vertex_hdl_t trace_vhdl;
|
|
|
|
/* ARGSUSED */
|
|
static void
|
|
vertex_trace_ref(vertex_hdl_t vhdl, graph_vertex_t *vertex)
|
|
{
|
|
}
|
|
|
|
/* ARGSUSED */
|
|
static void
|
|
vertex_trace_unref(vertex_hdl_t vhdl, graph_vertex_t *vertex)
|
|
{
|
|
}
|
|
|
|
static void
|
|
VERTEX_REF(vertex_hdl_t vhdl, graph_vertex_t *vertex)
|
|
{
|
|
if (vhdl == trace_vhdl)
|
|
vertex_trace_ref(vhdl, vertex);
|
|
|
|
(void)atomicAddInt(&vertex->v_refcnt, 1);
|
|
}
|
|
|
|
static int
|
|
VERTEX_UNREF(vertex_hdl_t vhdl, graph_vertex_t *vertex)
|
|
{
|
|
if (vhdl == trace_vhdl)
|
|
vertex_trace_unref(vhdl, vertex);
|
|
|
|
return(atomicAddInt(&vertex->v_refcnt, -1));
|
|
}
|
|
#endif /* DEBUG */
|