/*
 * Copyright (c) 2001-2003 Shiman Associates Inc. All Rights Reserved.
 * 
 * Permission is hereby granted, free of charge, to any person
 * obtaining a copy of this software and associated documentation
 * files (the "Software"), to deal in the Software without
 * restriction, including without limitation the rights to use, copy,
 * modify, merge, publish, distribute, sublicense, and/or sell copies
 * of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
 * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
 * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 *
 */
/*
 * Copyright (c) 2000, 2001 by Shiman Associates Inc. and Sun
 * Microsystems, Inc. All Rights Reserved.
 *
 * Permission is hereby granted, free of charge, to any person
 * obtaining a copy of this software and associated documentation
 * files (the "Software"), to deal in the Software without
 * restriction, including without limitation the rights to use, copy,
 * modify, merge, publish, distribute, sublicense, and/or sell copies
 * of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions: The above
 * copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT.  IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
 * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 *
 * Except as contained in this notice, the names of the authors or
 * copyright holders shall not be used in advertising or otherwise to
 * promote the sale, use or other dealings in this Software without
 * prior written authorization from the authors or copyright holders,
 * as applicable.
 *
 * All trademarks and registered trademarks mentioned herein are the
 * property of their respective owners. No right, title or interest in
 * or to any trademark, service mark, logo or trade name of the
 * authors or copyright holders or their licensors is granted.
 *
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <dlfcn.h>
#include "signal_handler.h"
#include "mas_internal.h"
#include "assembler.h"
#include "mas_dpi.h"
#include "mc.h"
#include "scheduler.h"
#include "devload.h"

#define MAX_LINE_LENGTH  256
#define MAX_PATH_ENTRIES 16
#define MAX_DEVICES      1024
#define MAX_PORTS        1024
#define MAS_MIN_SEGMENT  4096
#define MAS_RESERVED_INSTANCES 16
#define MAS_DEFAULT_VERBOSITY 
#define DEFAULT_SECS_TO_LIVE 2

#define MAS_DEVLIB_PREFIX  "libmas_"
#define MAS_DEVLIB_POSTFIX "_device.so"

/***********************************************************************
 *** This default key, value pair stuff is temporary.  Need run-time
 *** configuration.
 ***
 ***/

/**** platform specific or default device mappings that map a device
 **** interface name to a specific device name                       */
static struct mas_keyval _default_device_map[] =
{
#ifdef linux
    { "anx", "anx_oss" },
#endif
#ifdef sun
    { "anx", "anx_solaris" },
#endif
    { "", "" }, /* preserve terminator */
};
/***********************************************************************/

/* local prototypes *************************************/
static int32 read_profile_symbols( struct mas_device_profile*  );
static void  initialize_mas_path( void );
static void  append_to_mas_path( char* );
static void  append_csl_to_mas_path( char* csl );
static int32 find_next_free_instance( void );
static int32 find_next_free_port( void );
static void  add_profile( struct mas_device_profile* profile );
static void  add_assemblage( struct mas_assemblage* assemblage );
static int32 add_device_instance_to_assemblage( int32 di, char* assemblage_name );
static void  get_libname_from_device_name( char* name, char* return_library_name );
static int32 get_default_device( char* name, 
				 char* return_default_name );
static struct mas_device_profile* get_device_profile_from_name( char* device_name );
static void  destroy_profile( struct mas_device_profile* profile);
static void  destroy_cmatrix( struct mas_characteristic_matrix* cmatrix );
static int32 cmatrix_symbol_to_struct(void* sym, 
				      struct mas_characteristic_matrix* 
				      cmatrix);
static int32 read_profile_ports(void* sym, struct mas_device_profile* profile);
static uint32 next_event_id( void );
static int32 dc_match_cmatrix_row(struct mas_data_characteristic* dc, struct mas_characteristic_matrix* cmatrix );
static void initialize_core_profiles( void );
static struct mas_port* get_port( int32 portnum );
static struct mas_device* get_device( int32 di );
static struct mas_assemblage* get_assemblage_from_name( char* name );
static int32 generate_tracking_assemblage_name( struct mas_event* event, char* name );
static int32 setup_sch_move_data( struct mas_port *srcp, struct mas_port *snkp );
static int32 strike_sch_move_data( struct mas_port *srcp );
static int32 setup_dev_configure_port( struct mas_port *port );
static int32 setup_dev_disconnect_port( struct mas_port *port );


/* NOTE: static variables start with "_" */
/* Ports and device instances are kept in hash tables for easy random
   access.  Assemblages and device profiles are kept in linked
   lists */
static struct mas_device* _device_hash[MAX_DEVICES];
static struct mas_port*   _port_hash[MAX_PORTS];
static struct mas_device_profile* _profile_list_head;
static struct mas_assemblage* _assemblage_list_head;
static int32              _device_ctr = MAS_RESERVED_INSTANCES;
static int32              _master_device_profile_counter = 0;
static int32              _master_assemblage_counter = 0;
static int32              _master_port_counter   = 1; /* skip the 0th port */
static uint32             _master_event_counter = 1;
static char               _mas_path_list[MAX_PATH_ENTRIES+1][MAX_FNAME_LENGTH];
static char               _mas_path_list_initialized = 0;
static char               _assembler_is_initialized = 0;
static struct mas_event*  _event_queue_head = 0;
static struct mas_event*  _current_event = 0;
static struct mas_mc_cb*  _mc_cb;
    
/* list of actions.  preserve the terminator */
static char* _actions[] =  { "mas_asm_destroy_port", /*0*/
                             "mas_asm_make_port",/*1*/
                             "mas_asm_instantiate_device",/*2*/
                             "mas_asm_get_port_by_name",/*3*/
                             "mas_asm_get_device_by_name",/*4*/
                             "mas_asm_connect_source_sink",/*5*/
                             "mas_asm_connect_ports",/*6*/
                             "mas_get",/*7*/
                             "mas_asm_get_dc",/*8*/
                             "mas_asm_killall",/*9*/
                             "mas_asm_connect_named_ports",/*10*/
                             "mas_asm_disconnect_port",/*11*/
                             "mas_asm_terminate_device_instance",/*12*/
                             "mas_asm_teardown_device_instance",/*13*/
                             "mas_asm_destroy_device_instance",/*14*/
                             "mas_asm_make_tracking_assemblage",/*15*/
                             "mas_asm_terminate_tracking_assemblage",/*16*/
                             "mas_asm_cleanup_port",/*17*/
                             "mas_asm_connect_inline", /*18*/
                             "mas_asm_disconnect_inline", /*19*/
                             "mas_asm_add_tracking_assemblage_terminate_event", /*20*/
                             "" }; 


/***********************************************************************
 * MAS INTERNAL FUNCTIONS
 ***********************************************************************/


/***************************************************************************
 * masi_verify_mas_device
 *
 * arguments:
 *
 *  
 *
 * returns: error
 *
 ***************************************************************************/
int32
masi_verify_mas_device( char* name )
{
    int32  instance;
    int err = 0;
    int i;

    if ( !_mas_path_list_initialized )
        initialize_mas_path();
    
    masc_log_message( 0, "Loading device %s...", name);
    if ( ( err = mas_asm_instantiate_device( name, 0, "", &instance ) ) == 0)
    {
	masc_log_message( 0, "OK");
	masi_print_device_profile(_device_hash[instance]->profile, MAS_PRINT_LICENSE|MAS_PRINT_CMATRIX);
	for (i=0; i<_device_hash[instance]->profile->cmatrices; i++)
	    masi_print_characteristic_matrix( &_device_hash[instance]->profile->cmatrix[i] );
    }
    else 
    {
	masc_log_message( 0, "ERROR");
	if ( mas_get_merror(err) == MERR_BIGERROR) 
	    masc_logerror( err|MAS_ERR_ALERT, "Couldn't resolve most of the profile symbols.  Either you haven't included the profile or there are other unresolved symbols in the library.");
	if ( mas_get_merror(err) == MERR_ERROR)
	    masc_logerror( err|MAS_ERR_ALERT, "Couldn't resolve some of the profile symbols.");
    }

    return err;
}

/***********************************************************************
 * The following debugging helpers require data scoped to this file.
 * Need to fix.
 */
int32
masi_print_instantiated_devices( void )
{
    int i;

    masc_log_message( 0, "instantiated devices:");
    for (i=0; i<MAX_DEVICES; i++)
	if (_device_hash[i] != 0)
            if ( _device_hash[i]->profile )
                if(_device_hash[i]->profile->name)
                    masc_log_message( 0, "%04d: %s", i, _device_hash[i]->profile->name);
    
    return 0;
}

int32
masi_print_ports( void )
{
    int i;
    char* nullstr = "<undefined>";
    char* ptype[] =  { "none    ", "source  ", "sink    ", "reaction", "response" };
    int32 di, connport, pt;
    char* dname;
    char  connport_str[16];
    
    masc_log_message( 0, "port list:");
    masc_log_message( 0, "port: type     'port name       ' dev  'device name     ' connected port");
    masc_log_message( 0, "----- -------- ------------------ ---- ------------------ --------------");
    
    for (i=0; i<MAX_PORTS; i++)
    {
	if (_port_hash[i] != 0)
	{
            pt = _port_hash[i]->port_type;
            di = _port_hash[i]->device_instance;
            connport = _port_hash[i]->connected_port;
            
            if ( pt < 0 || pt > 4 )
                pt = 0;
            if ( di < 0 || di > MAX_DEVICES )
            {
                di = -1;
            }
            else
            {
                if ( _device_hash[di] == 0 )
                {
                    di = -1;
                }
                else
                {
                    if ( _device_hash[di]->profile )
                        dname = _device_hash[di]->profile->name;
                }
            }

            if ( di < 0 )
                dname = nullstr;

            if ( connport > 0 )
            {
                sprintf(connport_str, "%04d", connport );
            }
            else
            {
                sprintf(connport_str, "none" );
            }
            
                
            masc_log_message( 0, "%04d: %s '%-16s' %04d '%-16s' -> %s", i, ptype[pt], _port_hash[i]->name, di, dname, connport_str);
	}
    }

    return 0;
}

void
masi_print_loaded_device_libraries( void )
{
    struct mas_device_profile* profile;
    
    masc_log_message( 0, "Loaded device libraries:");

    if ( _profile_list_head == NULL )
    {
        masc_log_message( 0, "(none)");
    }
    else
    {
        for ( profile = _profile_list_head; profile != NULL; profile = profile->next )
        {
	    masc_log_message( 0, "%04d: %s", profile->id, profile->library_filename);
        }
    }
}

void
masi_print_all_profiles( void )
{
    struct mas_device_profile* profile;
    
    masc_log_message( 0, "Loaded device profiles:");

    if ( _profile_list_head == NULL )
    {
        masc_log_message( 0, "(none)");
    }
    else
    {
        for ( profile = _profile_list_head; profile != NULL; profile = profile->next )
        {
	    masi_print_device_profile( profile, 0 );
	    masc_log_message( 0, "");
        }
    }
}

void
masi_print_assemblages( void )
{
    struct mas_assemblage* asmb;

    masc_log_message( 0, "Active assemblages:");
    
    if ( _assemblage_list_head == NULL )
    {
        masc_log_message( 0, "(none)");
    }
    else
    {
        for ( asmb = _assemblage_list_head; asmb != NULL; asmb = asmb->next )
	    masi_print_assemblage( asmb );
    }
}


void
masi_print_assemblage( struct mas_assemblage* asmb )
{
    struct mas_devlist_node *dln;
    struct mas_device *device;
    char tstr[1024];
    char dstr[256];
    int len, cnt = 0;
    
    sprintf( tstr, "%04d '%s': ", asmb->id, asmb->name );

    for ( dln = asmb->device_list_head; dln != NULL; dln = dln->next )
    {
        len = strlen( tstr );
        if ( cnt > 0 )
        {
            strncat( tstr, ", ", 1024 - len - 1 );
            len += 2;
        }
        
        device = get_device( dln->di );
        if ( device != NULL && device->profile != NULL )
        {
            snprintf( dstr, 255, "%d (%s)", dln->di, device->profile->name );
        }
        else
        {
            snprintf( dstr, 255, "%d (*missing*)", dln->di );
        }

        strncat( tstr, dstr, 1024 - len - 1 );
        cnt++;
    }

    len = strlen( tstr );
    if ( len >= 1023 )
    {
        tstr[1020] = '.';
        tstr[1021] = '.';
        tstr[1022] = '.';
        tstr[1023] = 0;
        return;
    }

    masc_log_message(0, tstr);
}

void
masi_dump_server_state( void )
{
    masc_log_message(0, "");
    masc_log_message(0, "********************************************************************************");
    masc_log_message(0, "asm: dumping server state");
    masc_log_message(0, "");
    masi_print_ports();
    masc_log_message(0, "");
    masi_print_loaded_device_libraries();
    masc_log_message(0, "");
    masi_print_instantiated_devices();
    masc_log_message( 0, "" );
    masi_print_assemblages();
    masc_log_message(0, "");
    masi_print_event_queue(_event_queue_head);
    masc_log_message(0, "");
    masi_print_all_profiles();
    masc_log_message(0, "");
    mas_mc_print_clocks( _mc_cb );
    masc_log_message(0, "********************************************************************************");
    masc_log_message(0, "");
}

/* this is temporary */
char*
masi_get_value_from_key( struct mas_keyval* keyval_list, char* key )
{
    int i = 0;
    
    while ( keyval_list[i].key[0] != 0 )
    {
	if (strncmp( keyval_list[i].key, key, MAX_FNAME_LENGTH ) == 0)
	    break;
	i++;
    }

    if (keyval_list[i].key[0] == 0) return "";
    else return keyval_list[i].value;
}

/***********************************************************************
 * ASSEMBLER FUNCTIONS
 ***********************************************************************/

int32 
mas_asm_action_handler( struct mas_event* event )
{
    int32 retval = 0, err = 0;
    int32 device_instance, portnum;
    int32 source, sink;
    struct mas_data_characteristic dc;
    struct mas_package package, tpack;
    struct mas_data* data;
    void* payload;
    uint32 payload_len;
    char* name;
    char* key;
    struct mas_package argpack;
    struct mas_package rpack;
    void* predicate;
    int32 predlen;
    int16 n=0;
    int32 response;
    char *rkey = NULL;
    int send_retval = TRUE;

    predicate = event->predicate;
    predlen = event->predicate_length;
    response = event->response;
    _current_event = event; /* handling an assembler action */

    if ( response )
        masc_setup_package( &rpack, NULL, 0, MASC_PACKAGE_NOFREE );
    
    /* make sure we have set this event's action index */
    if ( ! event->valid_action_index )
    {
        /* count the defined actions */
        while ( *_actions[n] != 0 ) n++;
        
        event->action_index = masc_get_string_index(event->action_name, _actions, n);
        event->valid_action_index = TRUE;
    }
        
    /**
     ** Be sure to add your new action to the "actions" array defined
     ** at the top of this file.
     **
     **/
    switch (event->action_index)
    {
    case 0: /*mas_asm_destroy_port*/
	portnum = *(int32*)predicate;
	retval = mas_asm_destroy_port( portnum );
	/* discard the port number */
        break;
    case 1: /*mas_asm_make_port*/
	device_instance = *(int32*)predicate;
	name = (char*)predicate + sizeof(device_instance);
	retval = mas_asm_make_port(name, 0, device_instance, &portnum);
	/* discard the port number */
        break;
    case 2: /*mas_asm_instantiate_device*/
    {
        char tracking_assemblage_name[MAX_ASMB_NAME_LENGTH];

        generate_tracking_assemblage_name( event, tracking_assemblage_name );
        
        /* Setup package using event's predicate as the contents.
         * Don't free it on strike */ 
        masc_setup_package( &package, predicate, predlen, MASC_PACKAGE_NOFREE|MASC_PACKAGE_EXTRACT );
        /* don't copy the name */
        payload = NULL;
	masc_pullk_string( &package, "d", &name, FALSE );
        if ( masc_test_key( &package, "p" ) == 0 )
            masc_pullk_payload( &package, "p", &payload, &payload_len, TRUE );
	err = mas_asm_instantiate_device(name, payload, tracking_assemblage_name, &retval);
        if ( err < 0 ) retval = err;
        rkey = "di";
        masc_strike_package( &package );
    }
    break;
    case 3: /*mas_asm_get_port_by_name*/
        /* Setup package using event's predicate as the contents.
         * Don't free it on strike */ 
        masc_setup_package( &package, predicate, predlen, MASC_PACKAGE_NOFREE|MASC_PACKAGE_EXTRACT );
	masc_pull_int32( &package, &device_instance );
	masc_pull_string( &package, &name, FALSE ); /* don't copy name */
	err = masd_get_port_by_name(device_instance, name, &retval);
        if ( err < 0 ) retval = err;
        rkey = "pn";
	masc_strike_package( &package );
        break;
    case 4: /*mas_asm_get_device_by_name*/
        /* Setup package using event's predicate as the contents.
         * Don't free it on strike */ 
        masc_setup_package( &package, predicate, predlen, MASC_PACKAGE_NOFREE|MASC_PACKAGE_EXTRACT );
	masc_pull_string( &package, &name, FALSE );
	err = masd_get_device_by_name(name, &retval);
        if ( err < 0 ) retval = err;
        rkey = "di";
	masc_strike_package( &package );
        break;
    case 5: /*mas_asm_connect_source_sink*/
        /* Setup package using event's predicate as the contents.
         * Don't free it on strike */ 
        masc_setup_package( &package, predicate, predlen, MASC_PACKAGE_NOFREE|MASC_PACKAGE_EXTRACT );
	masc_pull_int32( &package, &source );
	masc_pull_int32( &package, &sink );
        /* pull the dc package out - don't bother copying it. */
	masc_pull_package( &package, &tpack, FALSE );
	masc_setup_dc( &dc, 16 );
        masc_unpack_dc( &tpack, &dc );
        masc_strike_package( &tpack );
	masc_log_message( MAS_VERBLVL_DEBUG, "asm: connecting port %d -> %d, with dc: ", source, sink);
#ifdef DEBUG
	masc_print_dc( &dc );
#endif
	retval = mas_asm_connect_source_sink(source, sink, &dc);
	masc_strike_package( &package );
        masc_strike_dc( &dc );
        break;
    case 6: /*mas_asm_connect_ports*/
        /* Setup package using event's predicate as the contents.
         * Don't free it on strike */ 
        masc_setup_package( &package, predicate, predlen, MASC_PACKAGE_NOFREE|MASC_PACKAGE_EXTRACT );
	masc_pull_int32( &package, &source );
	masc_pull_int32( &package, &sink );
	masc_log_message( MAS_VERBLVL_DEBUG, "asm: connecting port %d -> %d.", source, sink);
	retval = mas_asm_connect_ports(source, sink);
	masc_strike_package( &package );
        break;
    case 7: /*mas_get*/
    {
        struct mas_package *argpack_ptr;
        int send_req_info = FALSE;
        
        /* Setup package using event's predicate as the contents.
         * Don't free it on strike */ 
        masc_setup_package( &package, predicate, predlen, MASC_PACKAGE_NOFREE|MASC_PACKAGE_EXTRACT );
	masc_pull_string( &package, &key, FALSE );
        /* get the argument package, if its there */
        if ( package.members > 1 )
        {
            /* but don't bother copying */
            masc_pull_package( &package, &argpack, FALSE );
            argpack_ptr = &argpack;

            /* pull out the return portnumber, if necessary.  default
             * to response port. */
            if ( masc_test_key( &package, "retport" ) == 0 )
            {
                masc_pullk_int32( &package, "retport", &portnum );
                
                /* don't send the request info if this goes back to
                   the response port.  If it's going to a shared data
                   channel, send it. */
                if ( portnum != response )
                {
                    send_req_info = TRUE;
                    response = portnum;
                }
            }
        }
        else
        {
            argpack.contents = NULL;
            argpack_ptr = NULL;
        }

        retval = mas_asm_get( key, argpack_ptr, send_req_info, &rpack );

        if (retval == 0)
            send_retval = FALSE;

        if ( argpack.contents ) masc_strike_package( &argpack );
        break;
    }
    case 8: /*mas_asm_get_dc*/
    {
        struct mas_data_characteristic *dcp;
        
        /* Setup package using event's predicate as the contents.
         * Don't free it on strike */ 
        masc_setup_package( &package, predicate, predlen, MASC_PACKAGE_NOFREE|MASC_PACKAGE_EXTRACT );
	masc_pull_int32( &package, &portnum );
        rkey = "dc";
        
	if ( response != 0 )
	{
            retval = masd_get_data_characteristic( portnum, &dcp );
            if ( retval < 0 )
                goto done;

            /* don't also send the retval if we're successful */
            send_retval = FALSE;
            masc_pack_dc( &rpack, dcp );
	}
        masc_strike_package( &package );
        break;
    }
    case 9: /* mas_asm_killall */
        retval = mas_asm_killall();
        break;
    case 10: /*mas_asm_connect_named_ports*/
    {
        char *source_name;
        char *sink_name;
        int32 source_device;
        int32 sink_device;
        
        /* Setup package using event's predicate as the contents.
         * Don't free it on strike */ 
        masc_setup_package( &package, predicate, predlen, MASC_PACKAGE_NOFREE|MASC_PACKAGE_EXTRACT );
        masc_pull_int32( &package, &source_device );
        masc_pull_int32( &package, &sink_device );
	masc_pull_string( &package, &source_name, FALSE );
	masc_pull_string( &package, &sink_name, FALSE );

	retval = mas_asm_connect_named_ports(source_device, sink_device, source_name, sink_name);
	masc_strike_package( &package );
    }
    case 11: /*mas_asm_disconnect_port*/
    {
        /* Setup package using event's predicate as the contents.
         * Don't free it on strike */ 
        masc_setup_package( &package, predicate, predlen, MASC_PACKAGE_NOFREE|MASC_PACKAGE_EXTRACT );
        masc_pull_int32( &package, &portnum );

        retval = mas_asm_disconnect_port( portnum, TRUE );
	masc_strike_package( &package );
    }
    
    break;
    case 12: /*mas_asm_terminate_device_instance*/
    {
        int32 di;
        int32 secs_to_live;
        
        /* Setup package using event's predicate as the contents.
         * Don't free it on strike */ 
        masc_setup_package( &package, predicate, predlen, MASC_PACKAGE_NOFREE|MASC_PACKAGE_EXTRACT );
        masc_pull_int32( &package, &di );
        masc_pull_int32( &package, &secs_to_live );

        retval = mas_asm_terminate_device_instance( di, secs_to_live );
	masc_strike_package( &package );
    }
    break;
    case 13: /*mas_asm_teardown_device_instance*/
    {
        int32 di;
        
        /* Setup package using event's predicate as the contents.
         * Don't free it on strike */ 
        masc_setup_package( &package, predicate, predlen, MASC_PACKAGE_NOFREE|MASC_PACKAGE_EXTRACT );
        masc_pull_int32( &package, &di );

        retval = mas_asm_teardown_device_instance( di );
	masc_strike_package( &package );
    }
    break;
    case 14: /*mas_asm_destroy_device_instance*/
    {
        int32 di;
        
        /* Setup package using event's predicate as the contents.
         * Don't free it on strike */ 
        masc_setup_package( &package, predicate, predlen, MASC_PACKAGE_NOFREE|MASC_PACKAGE_EXTRACT );
        masc_pull_int32( &package, &di );

        retval = mas_asm_destroy_device_instance( di );
	masc_strike_package( &package );
    }
    break;
    case 15: /*mas_asm_make_tracking_assemblage*/
        retval = mas_asm_make_tracking_assemblage( event );
        break;
    case 16: /*mas_asm_terminate_tracking_assemblage*/
        retval = mas_asm_terminate_tracking_assemblage( event );
        break;
    case 17: /*mas_asm_cleanup_port*/
	portnum = *(int32*)predicate;
	retval = mas_asm_cleanup_port( portnum );
        break;
    case 18: /*mas_asm_connect_inline*/
    {
        int32 srca, snkb, srcc, snkd;
        struct mas_data_characteristic *ab_dc = NULL;
        struct mas_data_characteristic *cd_dc = NULL;
        int8 config;
        
        /* Setup package using event's predicate as the contents.
         * Don't free it on strike */ 
        masc_setup_package( &package, predicate, predlen, MASC_PACKAGE_NOFREE|MASC_PACKAGE_EXTRACT );
	masc_pullk_int32( &package, "srca", &srca );
	masc_pullk_int32( &package, "snkb", &snkb );
	masc_pullk_int32( &package, "srcc", &srcc );
	masc_pullk_int32( &package, "snkd", &snkd );
        masc_pullk_int8( &package, "config", &config );
        if ( masc_test_key(&package, "ab_dc" ) == 0 )
        {
            ab_dc = MAS_NEW( ab_dc );
            masc_pullk_package( &package, "ab_dc", &tpack, FALSE );
            masc_setup_dc( ab_dc, 16 );
            masc_unpack_dc( &tpack, ab_dc );
            masc_strike_package( &tpack );
        }
        
        if ( masc_test_key(&package, "cd_dc" ) == 0 )
        {
            cd_dc = MAS_NEW( cd_dc );
            masc_pullk_package( &package, "cd_dc", &tpack, FALSE );
            masc_setup_dc( cd_dc, 16 );
            masc_unpack_dc( &tpack, cd_dc );
            masc_strike_package( &tpack );
        }

        retval = mas_asm_connect_inline(srca, snkb, ab_dc, srcc, snkd, cd_dc, config);
	masc_strike_package( &package );
        /* don't free the dc's - they're used by connect_inline!  */
        break;
    }
    case 19: /*mas_asm_disconnect_inline*/
    {
        int32 srca, snkb, srcc, snkd;
        int8 config;
        struct mas_data_characteristic *dcp = NULL;

        /* Setup package using event's predicate as the contents.
         * Don't free it on strike */ 
        masc_setup_package( &package, predicate, predlen, MASC_PACKAGE_NOFREE|MASC_PACKAGE_EXTRACT );
	masc_pullk_int32( &package, "srca", &srca );
	masc_pullk_int32( &package, "snkb", &snkb );
	masc_pullk_int32( &package, "srcc", &srcc );
	masc_pullk_int32( &package, "snkd", &snkd );
        masc_pullk_int8( &package, "config", &config );
        if ( masc_test_key(&package, "dc" ) == 0 )
        {
            dcp = MAS_NEW( dcp );
            masc_pullk_package( &package, "dc", &tpack, FALSE );
            masc_setup_dc( dcp, 16 );
            masc_unpack_dc( &tpack, dcp );
            masc_strike_package( &tpack );
        }
        
        retval = mas_asm_disconnect_inline(srca, snkb, srcc, snkd, dcp, config);
	masc_strike_package( &package );
        /* don't free the dc - its used by disconnect_inline*/
        break;
    }
    case 20: /*mas_asm_add_tracking_assemblage_terminate_event*/
    {
        struct mas_event *term_event;

        /* unpack the terminate event from this event's predicate
           package */
        term_event = MAS_NEW( term_event );
        if ( term_event == NULL )
        {
            retval = mas_error(MERR_MEMORY);
            goto done;
        }

        masc_setup_package( &package, event->predicate, 0, MASC_PACKAGE_STATIC|MASC_PACKAGE_EXTRACT );
        masc_unpack_event( &package, term_event );
        masc_strike_package( &package );

        retval = mas_asm_add_tracking_assemblage_terminate_event( event, term_event );
        break;
    }
    default:
        retval = mas_error(MERR_INVALID);
    }

 done:
    /* No longer handling assembler action */
    _current_event = 0;

    if ( response )
    {
        if ( send_retval )
        {
            if ( retval < 0 || rkey == NULL )
                rkey = "err";
            /* do response package */
            masc_pushk_int32( &rpack, rkey, retval );
        }

        masc_finalize_package( &rpack );

        /* stuff data struct */
        data = MAS_NEW( data );
        data->length = rpack.size;
        data->allocated_length = rpack.allocated_size;
        data->segment = rpack.contents;
        masd_post_data( response, data );
    }

    return retval;
}
    
int32
mas_asm_init( void )
{
    int i;
    
    initialize_mas_path();
    
    for (i=0; i<MAX_PORTS; i++)
	_port_hash[i] = 0;

    for (i=0; i<MAX_DEVICES; i++)
	_device_hash[i] = 0;

    _profile_list_head = NULL;
    _assemblage_list_head = NULL;
    
    /* allocate null event queue head */
    _event_queue_head = MAS_NEW( _event_queue_head );
    
    _assembler_is_initialized = 1;

    _mc_cb = mas_mc_init();

    initialize_core_profiles();
    
    return 0;
}

int32
mas_asm_record_action_stats( struct mas_event* event, uint32 action_us )
{
    int32 err;
    struct mas_device_profile* profile;
    struct mas_stats* stats;

    /* don't record stats if the action took more than 5 minutes to
       complete. */
    if ( action_us > 300000000 ) return 0;

    /* make sure we have an action index */
    if ( ( err = mas_asm_set_event_action_index( event ) ) < 0 )
	return err;
    
    /* for convenience */
    profile = _device_hash[event->device_instance]->profile;

    if ( profile == 0 ) return mas_error(MERR_NULLPTR);

    /* again, for convenience */
    stats = &profile->action_wallclock_stats[event->action_index];

    /* update the statistics */
    masc_stats_update( stats, action_us );
    
    return 0;
}

int32
mas_asm_instantiate_device( char* name, void* predicate, char* assemblage_name, int32* retval_instance  )
{
    struct mas_device*         device;
    struct mas_device_profile* profile;
    struct mas_assemblage*     assemblage;
    int32                      err;
    char                       library_name[MAX_FNAME_LENGTH];
    char                       device_name[MAX_FNAME_LENGTH];
    int                        i;
    int32                      holder;
    struct mas_characteristic_matrix* cmatrix;
    void*                      init_func;
    struct mas_package         package;

   
    masc_entering_log_level( "asm: mas_asm_instantiate_device" );

    if(!assemblage_name) assemblage_name = "NULL";

    masc_log_message( MAS_VERBLVL_DEBUG, "asm: instantiating device '%s' in assemblage '%s'.", name, assemblage_name );

    device = MAS_NEW( device );
    
    if ( device == NULL )
    {
        /* do not try to free device */
        masc_exiting_log_level();
        return mas_error(MERR_MEMORY); 
    }
    
    /* find the next free device on the master list */
    if (find_next_free_instance() < 0)
    {
        /* free device, but don't null the hash location */
        masc_rtfree( device );
        masc_exiting_log_level();
        return mas_error(MERR_MEMORY); 
    }

    /* take the slot */
    _device_hash[_device_ctr] = device;
    
    /* remember our instantiation index */
    device->instance = _device_ctr;

    /* null out the state pointer */
    device->state = 0;

    /* see if there's any default device for this name */
    if (get_default_device( name, device_name ) < 0 )
    {
        masc_log_message( MAS_VERBLVL_ERROR, "asm: [error] unknown device '%s'", name );
        err = mas_error(MERR_NOTDEF);
        goto fail;
    }

    /* Is this a new assemblage? */
    assemblage = get_assemblage_from_name( assemblage_name );
    if ( assemblage == NULL )
    {
        /* and the name isn't blank */
        if ( assemblage_name != NULL && assemblage_name[0] != 0 )
        {
            /* Yes, create it. */
            err = mas_asm_make_assemblage( assemblage_name );
            if ( err < 0 )
            {
                masc_log_message( MAS_VERBLVL_ERROR, "asm: [error] couldn't create assemblage '%s'", assemblage_name );
                err = mas_error(MERR_NOTDEF);
                goto fail;
            }
        }
    }

    /* see if we've already loaded the library */
    profile = get_device_profile_from_name( device_name );
    if ( profile != NULL )
    {
	/* object code is already loaded, we have the profile. */
	device->profile = profile;
        if ( !device->profile->reentrant )
        {
            masc_log_message(MAS_VERBLVL_ERROR, "asm: This device library has already been instantiated and it is non-reentrant.");
            masc_log_message(MAS_VERBLVL_ERROR, "asm: Device '%s' cannot be instantiated.", device_name );
            err = mas_error(MERR_INVALID);
            goto fail;
        }

        device->profile->refcount++;
    }
    else /* device object code isn't loaded, we have to do it. */
    {
	/* try to deduce the library filename from the given device
	   name ("net" is "libmas_net_device.so") */
	get_libname_from_device_name( device_name, library_name );
	
	/* allocate space for the profile */
	if ( (device->profile = MAS_NEW(device->profile)) == 0)
        {
	    err = mas_error(MERR_MEMORY);
            goto fail;
        }

        device->profile->refcount++;
        
	/* load the device code */
	err = load_plugin_library( library_name, _mas_path_list, &device->profile->handle ); 
	if (err < 0 || device->profile->handle == 0)
        {
            masc_log_message( MAS_VERBLVL_ERROR, "asm: ERROR: Couldn't load device library '%s'.", library_name );
            err = mas_error(MERR_FILE_CANNOT_OPEN);
            goto profile_free_fail;
        }
        
	/* add profile to the master list */
        add_profile( device->profile );
        
	/* store the library's file name in the profile */
	strncpy(device->profile->library_filename, library_name, MAX_FNAME_LENGTH);
	device->profile->library_filename[MAX_FNAME_LENGTH-1] = 0;
	
	/* grab the profile */
	if ( ( err = read_profile_symbols( device->profile ) ) < 0 )
        {
            masc_log_message( MAS_VERBLVL_ERROR, "asm: [ERROR] Couldn't read library profile symbols." );
            goto profile_free_fail;
        }

        /* find the mas_dev_init_library symbol and call it */
        init_func = resolve_symbol_by_name( device->profile->handle, "mas_dev_init_library" );
        if ( init_func && *(int*)init_func ) ((int32 (*)())init_func)();
    }

    /* create the reaction port */
    err = mas_asm_make_port("reaction", MAS_REACTION, device->instance, &holder); 
    if ( err < 0 )
    {
        masc_log_message( MAS_VERBLVL_ERROR, "asm: [ERROR] Couldn't create reaction port." );
        goto profile_free_fail;
    }

    
    /* allocate all the static ports for the device */
    for (i=0; i<device->profile->ports; i++)
    {
	err = mas_asm_make_port(device->profile->port_names[i], device->profile->port_types[i], device->instance, &holder);
	if ( err < 0 ) goto profile_free_fail;
        
	err = masd_get_cmatrix_from_name( device->instance, device->profile->port_cmatrices[i], &cmatrix ); 
	if ( err < 0 ) goto profile_free_fail;

	err = masd_set_port_cmatrix ( holder, cmatrix );
	if ( err < 0 ) goto profile_free_fail;
    }

    /* add the device to the assemblage, if the assemblage name isn't blank. */
    if ( assemblage_name != NULL && assemblage_name[0] != 0 )
    {
        err = add_device_instance_to_assemblage( device->instance, assemblage_name );
        if ( err < 0 )
        {
            masc_log_message( MAS_VERBLVL_DEBUG, "asm: [warning] Failed to add device to assemblage '%s'.  Leaving in null assemblage.", assemblage_name );
        }
    }
    
    /* return the instance number */
    *retval_instance = device->instance;
    
    /* schedule the dev_init */
    mas_asm_schedule_event_simple( device->instance, "mas_dev_init_instance", predicate ); 

    /* schedule the master clock hook, if applicable */
    for (i=0; i<device->profile->clocks; i++)
    {
        masc_setup_package( &package, NULL, 0, MASC_PACKAGE_NOFREE );
        masc_pushk_int32( &package, "device_instance", device->instance );
        masc_pushk_string( &package, "clock", device->profile->clock_names[i] );
        masc_finalize_package( &package );
        mas_asm_schedule_event_simple( MAS_MC_INSTANCE, "mas_mc_add_clock", package.contents );
        masc_strike_package( &package );
    }

    goto success;
        
 success:
    masc_exiting_log_level();
    return 0;

 profile_free_fail:
    destroy_profile( device->profile );
    device->profile = 0;
    /* fallthrough */
 fail:
    masc_rtfree( device );
    _device_hash[_device_ctr] = 0;
    masc_exiting_log_level();
    return err;
}

int32
mas_asm_killall( void )
{
    int32 i, err;
    
    masc_entering_log_level( "mas_asm_killall" );

    masc_log_message( MAS_VERBLVL_COREERR, "" );
    masc_log_message( MAS_VERBLVL_COREERR, "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@");
    masc_log_message( MAS_VERBLVL_COREERR, "@ TERMINATING DEVICES                                           @");
    masc_log_message( MAS_VERBLVL_COREERR, "@ MAS WILL EXIT IN %d SECONDS                                    @", DEFAULT_SECS_TO_LIVE * 2);
    masc_log_message( MAS_VERBLVL_COREERR, "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@");
    masc_log_message( MAS_VERBLVL_COREERR, "" );
    
    /* Terminate all active devices. */
    for (i = MAS_RESERVED_INSTANCES; i<MAX_DEVICES; i++)
    {
	if (_device_hash[i] != 0 )
            mas_asm_terminate_device_instance( i, DEFAULT_SECS_TO_LIVE );
    }
    
    masi_dump_server_state();
    
    err = mas_asm_schedule_event( MAS_SCH_INSTANCE, "mas_sch_end", 0, DEFAULT_SECS_TO_LIVE * 2, 0, TRUE, MAS_PRIORITY_YESTERDAY, 0, 0, 0 );
    if ( err < 0 ) masc_logerror( err | MAS_ERR_CRITICAL, "Couldn't schedule shutdown event.  Uh oh.");

    masc_exiting_log_level();
    
    return 0;
}

int32
mas_asm_make_assemblage(char* name)
{
    struct mas_assemblage* assemblage;

    masc_entering_log_level( "asm: mas_asm_make_assemblage" );

    masc_log_message( MAS_VERBLVL_DEBUG, "asm: creating assemblage '%s'", name );

    if ( name == NULL )
        return 0;
    
    /* check name for uniqueness over all assemblages */
    for ( assemblage = _assemblage_list_head; assemblage != NULL; assemblage = assemblage->next )
    {
        if ( strncmp( assemblage->name, name, MAX_ASMB_NAME_LENGTH-1 ) == 0 )
        {
            masc_log_message( MAS_VERBLVL_ERROR, "asm: [ERROR] assemblage name is not unique.");
            masc_exiting_log_level();
            return mas_error( MERR_INVALID );
        }
    }
    
    assemblage = MAS_NEW( assemblage );

    /* bytecopy the assemblage name */
    strncpy( assemblage->name, name, MAX_ASMB_NAME_LENGTH-1 );

    add_assemblage( assemblage );
    
    masc_exiting_log_level();
    return 0;
}

int32
mas_asm_make_tracking_assemblage(struct mas_event* event)
{
    char name[MAX_ASMB_NAME_LENGTH];
    int32 err;

    masc_entering_log_level( "asm: mas_asm_make_tracking_assemblage" );

    masc_log_message( MAS_VERBLVL_DEBUG, "asm: creating tracking assemblage for device %d, subscript %u", event->source_device_instance, event->source_device_subscript );

    err = generate_tracking_assemblage_name( event, name );
    if ( err < 0 )
    {
        masc_log_message( MAS_VERBLVL_ERROR, "asm: [ERROR] unknown device %d.", event->source_device_instance);
        masc_exiting_log_level();
        return mas_error( MERR_INVALID );
    }

    err = mas_asm_make_assemblage( name );
    if ( err < 0 )
    {
        masc_exiting_log_level();
        return err;
    }
    
    masc_exiting_log_level();
    return 0;
}

int32
mas_asm_add_tracking_assemblage_terminate_event(struct mas_event* track_event, struct mas_event *event)
{
    char name[MAX_ASMB_NAME_LENGTH];
    int32 err;

    masc_entering_log_level( "asm: mas_asm_add_tracking_assemblage_terminate_event" );

    err = generate_tracking_assemblage_name( track_event, name );
    if ( err < 0 )
    {
        masc_log_message( MAS_VERBLVL_ERROR, "asm: [ERROR] unknown device %d.", track_event->source_device_instance);
        masc_exiting_log_level();
        return mas_error( MERR_INVALID );
    }

    err = mas_asm_add_assemblage_terminate_event( name, event );
    if ( err < 0 )
    {
        masc_exiting_log_level();
        return err;
    }
    
    masc_exiting_log_level();
    return 0;
}

int32
mas_asm_add_assemblage_terminate_event( char *name, struct mas_event *event )
{
    struct mas_assemblage *assemblage;
    struct mas_event *e;
    
    masc_entering_log_level( "asm: mas_asm_add_assemblage_terminate_event" );

    masc_log_message( MAS_VERBLVL_DEBUG, "asm: adding terminate event for assemblage %s", name );

    /* check name for uniqueness over all assemblages */
    for ( assemblage = _assemblage_list_head; assemblage != NULL; assemblage = assemblage->next )
    {
        if ( strncmp( assemblage->name, name, MAX_ASMB_NAME_LENGTH-1 ) == 0 )
            break;
    }
    
    if ( assemblage == NULL )
    {
        masc_log_message( MAS_VERBLVL_ERROR, "asm: [ERROR] unknown assemblage.");
        masc_exiting_log_level();
        return mas_error( MERR_INVALID );
    }

    event->next = NULL;

    if ( assemblage->terminate_events )
    {
        /* find the tail */
        for ( e = assemblage->terminate_events; e->next != NULL;  )
            e = e->next;
        e->next = event;
    }
    else
    {
        /* handle the head case */
        assemblage->terminate_events = event;
    }

    masc_exiting_log_level();
    return 0;
}

int32
mas_asm_terminate_tracking_assemblage(struct mas_event* event)
{
    char name[MAX_ASMB_NAME_LENGTH];
    int32 err;

    masc_entering_log_level( "asm: mas_asm_make_tracking_assemblage" );

    masc_log_message( MAS_VERBLVL_DEBUG, "asm: terminating tracking assemblage for device %d, subscript %u", event->source_device_instance, event->source_device_subscript );

    err = generate_tracking_assemblage_name( event, name );
    if ( err < 0 )
    {
        masc_log_message( MAS_VERBLVL_ERROR, "asm: [ERROR] unknown device %d.", event->source_device_instance);
        masc_exiting_log_level();
        return mas_error( MERR_INVALID );
    }

    err = mas_asm_terminate_assemblage( name, DEFAULT_SECS_TO_LIVE );
    if ( err < 0 )
    {
        masc_exiting_log_level();
        return err;
    }
    
    masc_exiting_log_level();
    return 0;
}

int32
mas_asm_terminate_assemblage(char* name, int32 secs_to_live)
{
    struct mas_assemblage *assemblage;
    struct mas_devlist_node *dln, *next_dln;
    struct mas_device *device;
    struct mas_package package;
    struct mas_event *event;
    
    masc_entering_log_level( "asm: mas_asm_terminate_assemblage" );

    masc_log_message( MAS_VERBLVL_DEBUG, "asm: terminating assemblage '%s'", name );

    /* check name for uniqueness over all assemblages */
    for ( assemblage = _assemblage_list_head; assemblage != NULL; assemblage = assemblage->next )
    {
        if ( strncmp( assemblage->name, name, MAX_ASMB_NAME_LENGTH-1 ) == 0 )
            break;
    }
    
    if ( assemblage == NULL )
    {
        masc_log_message( MAS_VERBLVL_ERROR, "asm: [ERROR] unknown assemblage.");
        masc_exiting_log_level();
        return mas_error( MERR_INVALID );
    }

    /* schedule the terminate events */
    while ( assemblage->terminate_events )
    {
        event = assemblage->terminate_events;
        assemblage->terminate_events = event->next;

        /* do each five seconds from now, just to give everything a
         * chance to calm down. */
        event->clkid = MAS_MC_SYSCLK_US; /* to make sure */
        masc_get_short_usec_ts( &event->act_time );
        event->act_time += 5000000; /* 5 seconds */

        mas_asm_schedule_event_struct( event );
    }
    
    /* For each device in the assemblage... */
    for ( dln = assemblage->device_list_head; dln; dln = next_dln )
    {
        next_dln = dln->next; /* we'll delete dln this loop. */
        device = get_device( dln->di );
        if ( device )
        {

            /* Decrease the reference counter. */
            device->refcount--;

            /* Schedule mas_asm_terminate_device_instance if the
               device has a zero refcount. */
            if ( device->refcount <= 0 )
            {
                masc_log_message( MAS_VERBLVL_DEBUG, "asm: requesting termination of member device %d.", dln->di );
                masc_setup_package( &package, NULL, 0, MASC_PACKAGE_NOFREE );
                masc_pushk_int32( &package, "device_instance", dln->di );
                masc_pushk_int32( &package, "secs_to_live", secs_to_live );
                masc_finalize_package( &package );
                mas_asm_schedule_event_simple(MAS_ASM_INSTANCE, "mas_asm_terminate_device_instance", package.contents );
                masc_strike_package( &package );
            }
            else
            {
                masc_log_message( MAS_VERBLVL_DEBUG, "asm: member device %d is claimed by other assemblages; no action is taken.", dln->di );
            }
            
        }
        masc_rtfree( dln );
    }

    /* Destroy the assemblage structure. */
    if ( assemblage->prev )
        assemblage->prev->next = assemblage->next;
    if ( assemblage->next )
        assemblage->next->prev = assemblage->prev;
    masc_rtfree( assemblage );
    
    masc_exiting_log_level();
    return 0;
}

/* Destroying a device requires three steps: terminate, teardown, and
 * then destroy. */

int32
mas_asm_terminate_device_instance(int32 di, int32 secs_to_live)
{
    struct mas_package package;

    masc_entering_log_level( "asm: mas_asm_terminate_device_instance" );

    masc_log_message( MAS_VERBLVL_DEBUG, "asm: tearing down device %d in %d seconds", di, secs_to_live );

    if ( di < MAS_RESERVED_INSTANCES )
    {
        masc_log_message( MAS_VERBLVL_WARNING, "asm: [warning] server-built-in device %d cannot be terminated.", di );
        masc_exiting_log_level();
        return mas_error(MERR_NOTDEF);
    }
        
    /* Does this device exist? */
    if (get_device( di ) == 0)
    {
        masc_log_message( MAS_VERBLVL_WARNING, "asm: [warning] no device %d", di );
        masc_exiting_log_level();
        return mas_error(MERR_NOTDEF);
    }
    
    
    /* Schedule device action mas_dev_terminate on the device. */
    mas_asm_schedule_event_simple( di, "mas_dev_terminate", NULL ); 
    
    /* Schedule assembler action 'mas_asm_teardown_device_instance'
       secs_to_live seconds in the future with the packaged device
       instance number as the predicate. */
    masc_setup_package( &package, NULL, 0, MASC_PACKAGE_NOFREE );
    masc_pushk_int32( &package, "device_instance", di );
    masc_finalize_package( &package );
    mas_asm_schedule_event(MAS_ASM_INSTANCE, "mas_asm_teardown_device_instance", package.contents, secs_to_live, 0, TRUE, MAS_PRIORITY_ROUNDTUIT, 0, 0, NULL );
    masc_strike_package( &package );

    masc_exiting_log_level();
    return 0;
}

int32
mas_asm_teardown_device_instance(int32 di)
{
    struct mas_event *event, *next_event;
    struct mas_port* port;
    struct mas_package package;
    int32 pn;

    masc_entering_log_level( "asm: mas_asm_teardown_device_instance" );

    masc_log_message( MAS_VERBLVL_DEBUG, "asm: tearing down device %d", di );

    if ( di < MAS_RESERVED_INSTANCES )
    {
        masc_log_message( MAS_VERBLVL_WARNING, "asm: [warning] server-built-in device %d cannot be torn down.", di );
        masc_exiting_log_level();
        return mas_error(MERR_NOTDEF);
    }
        
    /* Does this device exist? */
    if (get_device( di ) == 0)
    {
        masc_log_message( MAS_VERBLVL_WARNING, "asm: [warning] no device %d", di );
        masc_exiting_log_level();
        return mas_error(MERR_NOTDEF);
    }
    
    /* Delete all events associated with the device from the
       scheduler's queue. */
    for (event = _event_queue_head; event != NULL; event = next_event )
    {
        /* Keep track of the next event in case we delete this one. */
        next_event = event->next;
        if ( event->device_instance == di )
        {
            masc_log_message( MAS_VERBLVL_DEBUG, "asm: deleting associated event %d '%s'", event->id, event->action_name );
            masc_strike_event( event );
            masc_rtfree( event );
        }
    }

    /* Disconnect all connected ports that belong to this device. */
    for (pn = 0; pn < MAX_PORTS; pn++ )
    {
        port = get_port( pn );
        if ( port != NULL )
            if ( port->device_instance == di && port->connected_port )
                mas_asm_disconnect_port( pn, TRUE );
    }

    /* Schedule device action mas_dev_exit_instance on device. */
    mas_asm_schedule_event_simple( di, "mas_dev_exit_instance", NULL ); 

    /* schedule assembler action mas_asm_destroy_device_instance with
     * predicate packaged device instance number */
    masc_setup_package( &package, NULL, 0, MASC_PACKAGE_NOFREE );
    masc_pushk_int32( &package, "device_instance", di );
    masc_finalize_package( &package );
    mas_asm_schedule_event_simple( MAS_ASM_INSTANCE, "mas_asm_destroy_device_instance", package.contents ); 
    masc_strike_package( &package );
    
    masc_exiting_log_level();

    return 0;
}

int32
mas_asm_destroy_device_instance(int32 di)
{
    struct mas_device* device;
    struct mas_port* port;
    int32 pn;

    masc_entering_log_level( "asm: mas_asm_destroy_device_instance" );

    masc_log_message( MAS_VERBLVL_DEBUG, "asm: destroying device %d", di );

    if ( di < MAS_RESERVED_INSTANCES )
    {
        masc_log_message( MAS_VERBLVL_WARNING, "asm: [warning] server-built-in device %d cannot be destroyed.", di );
        masc_exiting_log_level();
        return mas_error(MERR_NOTDEF);
    }
        
    /* Does this device exist? */
    device = get_device( di );
    if (device == 0)
    {
        masc_log_message( MAS_VERBLVL_WARNING, "asm: [warning] no device %d", di );
        masc_exiting_log_level();
        return mas_error(MERR_NOTDEF);
    }

    /* destroy the device's allocated ports */
    for (pn = 0; pn < MAX_PORTS; pn++ )
    {
        port = get_port( pn );
        if ( port != NULL )
            if ( port->device_instance == di )
                mas_asm_destroy_port( pn );
    }
    
    /* DON'T free the state memory - we depend on the device to do this. */

    /* decrease the reference count of the profile we were using */
    device->profile->refcount--;

    /* If the refcount's zero, unload the library. */
    if ( device->profile->refcount == 0 )
    {
        masc_log_message( MAS_VERBLVL_DEBUG, "asm: device profile's refcount is zero.");
        mas_asm_unload_device_library( device->profile->name );
    }
    
    masc_rtfree( device );
    
    /* free this instance number */
    _device_hash[di] = 0;

    masc_exiting_log_level();
    
    return 0;
}


int32
mas_asm_unload_device_library( char* device_name )
{
    struct mas_device_profile* profile;
    void*                      exit_func;

    masc_entering_log_level( "asm: mas_asm_unload_device_library" );

    masc_log_message( MAS_VERBLVL_DEBUG, "asm: unloading library for device '%s'.", device_name );

    profile = get_device_profile_from_name( device_name );
    if ( profile == 0 )
    {
        masc_log_message( MAS_VERBLVL_WARNING, "asm: [warning] no matching profile for device named '%s'", device_name );
        masc_exiting_log_level();
	return mas_error(MERR_NOTDEF);
    }

    /* make sure no devices are still using this profile */
    if ( profile->refcount )
    {
        masc_log_message( MAS_VERBLVL_WARNING, "asm: [warning] this library is still in use.", device_name );
        masc_exiting_log_level();
        return mas_error(MERR_ERROR);
    }
    
    /* find the mas_dev_exit_library symbol and call it */
    exit_func = resolve_symbol_by_name( profile->handle, "mas_dev_exit_library" );
    if ( exit_func ) ((int32 (*)())exit_func)();
    
    if ( unload_plugin_library( profile->handle ) < 0 )
    {
        masc_log_message( MAS_VERBLVL_ERROR, "asm: [ERROR] error unloading library." );
        masc_exiting_log_level();
	return mas_error(MERR_ERROR);
    }

    /* destroy the device profile */
    destroy_profile( profile );
    masc_exiting_log_level();
    return 0;
}

/* type of 0 is not error... it's just undefined */
int32
mas_asm_make_port(char* name, int16 type, int32 device_instance, int* retval_portnum)
{
    struct mas_port* port;

    masc_entering_log_level( "asm: mas_asm_make_port" );

    masc_log_message( MAS_VERBLVL_DEBUG, "asm: creating port '%s' on device %d.", name, device_instance );

    /* Does this device exist? */
    if (get_device( device_instance ) == 0)
    {
        masc_log_message( MAS_VERBLVL_WARNING, "asm: [warning] no device %d", device_instance );
        masc_exiting_log_level();
        return mas_error(MERR_NOTDEF);
    }
    
    masc_exiting_log_level();
    
    if (find_next_free_port() < 0)
	return mas_error(MERR_MEMORY);

    port = MAS_NEW( port );
    if ( port == NULL )
	return mas_error(MERR_MEMORY);

    /* add the port to the list */
    _port_hash[_master_port_counter] = port;
    *retval_portnum = _master_port_counter; /* set return value */
    port->portnum = _master_port_counter;

    strncpy(port->name, name, MAX_PORT_NAME_LENGTH);
    port->name[MAX_PORT_NAME_LENGTH-1] = 0;
    port->port_type = type;
    port->characteristic_matrix = 0;
    port->configured_data_characteristic = 0;
    port->data = 0;
    port->data_tail = 0;
    port->connected_port = 0;
    port->device_instance = device_instance;

    return 0;
}

int32
mas_asm_cleanup_port( int32 portnum )
{
    struct mas_data* d;
    struct mas_port* port;
    struct mas_event* event;
    struct mas_event* next_event;
    int i, j;
    int killit = FALSE;
    int32 err;

    masc_entering_log_level( "asm: mas_asm_cleanup_port" );

    masc_log_message( MAS_VERBLVL_DEBUG, "asm: cleaning up port %d", portnum );

    /* Does this port exist? */
    port = get_port( portnum );
    if ( port == NULL )
    {
        masc_log_message( MAS_VERBLVL_WARNING, "asm: [warning] no port %d", portnum );
        masc_exiting_log_level();
	return mas_error(MERR_NOTDEF);
    }

    /* Disconnect it if it's still connected. */
    if ( port->connected_port )
        err = mas_asm_disconnect_port( portnum, FALSE );

    /* Destroy any events that depend on this port. */
    for ( event = _event_queue_head->next; event != NULL; event = next_event )
    {
	next_event = event->next; /* must set before we destroy event */
	if ( event->num_port_dependencies )
	{
            /* test all port dependencies */
	    for (i=0; i<event->num_port_dependencies; i++)
	    {
                /* If we find a match, we have to remove that one
                   dependency, and shift the remaining array elements
                   up one. */
		if ( event->port_dependencies[i] == portnum )
                {
                    masc_log_message( MAS_VERBLVL_DEBUG, "asm: removing dependency from event %d '%s'", event->id, event->action_name );

                    /* loop from this element to the next-to-last */
                    for ( j=i; j < event->num_port_dependencies - 1; j++ )
                    {
                        event->port_dependencies[j] = event->port_dependencies[j+1];
                    }
                    event->num_port_dependencies--;
                    /* If no remaining dependencies in this matching
                       event, flag it to be destroyed below. */
                    if ( event->num_port_dependencies == 0 )
                        killit = TRUE;
                }
	    }
	}

        /* Event depended on this port, and there's no remaining
           dependencies. */
	if ( killit )
        {
            masc_log_message( MAS_VERBLVL_DEBUG, "asm: destroying dependent event %d", event->id );
            masc_strike_event( event );
            masc_rtfree( event );
            killit = FALSE;
        }
    }

    /* Destroy any remaining data on the port */
    /* Shift off the data segment's in the port's queue and destroy,
       one at a time. */
    i=0;
    while (port->data)
    {
        d = port->data;
        port->data = d->next;
        masc_strike_data( d );
        masc_rtfree( d );
        i++;
    }
    port->data_tail = NULL;
    
    if ( i > 0 )
        masc_log_message( MAS_VERBLVL_DEBUG, "asm: destroyed %d remaining data segments in the port's queue", i );

    /* Destroy the dc if it's there. */
    if ( port->configured_data_characteristic )
    {
        masc_strike_dc( port->configured_data_characteristic );
        masc_rtfree( port->configured_data_characteristic );
        port->configured_data_characteristic = NULL;
    }

    /* We don't destroy the port's cmatrix, that's just a pointer to a
       device profile member. */
    
    masc_exiting_log_level();
    
    return 0;
}

int32
mas_asm_destroy_port( int32 portnum )
{
    int32 err;
    struct mas_port* port;

    masc_entering_log_level( "asm: mas_asm_destroy_port" );

    /* Does this port exist? */
    port = get_port( portnum );
    if ( port == NULL )
    {
        masc_log_message( MAS_VERBLVL_WARNING, "asm: [warning] no port %d", portnum );
        masc_exiting_log_level();
	return mas_error(MERR_NOTDEF);
    }

    /* First, clean up all the associated data structures (events,
     * data, dc, etc.) */
    err = mas_asm_cleanup_port( portnum );
    if ( err < 0 )
    {
        masc_log_message( MAS_VERBLVL_ERROR, "asm: [error] failed cleaning up port %d.", portnum );
    }
    
    masc_log_message( MAS_VERBLVL_DEBUG, "asm: destroying port %d", portnum );

    /* Destroy the port. */
    masc_rtfree(port);
    _port_hash[portnum] = 0;

    masc_exiting_log_level();
    
    return 0;
}


int32
mas_asm_disconnect_port( int32 portnum, int cleanup_ports )
{
    struct mas_port *port, *cport;
    int32 err;

    masc_entering_log_level( "asm: mas_asm_disconnect_port" );

    masc_log_message( MAS_VERBLVL_DEBUG, "asm: disconnecting port %d", portnum );

    /* Does this port exist? */
    port = get_port( portnum );
    if ( port == NULL )
    {
        masc_log_message( MAS_VERBLVL_WARNING, "asm: [warning] no port %d", portnum );
        masc_exiting_log_level();
	return mas_error(MERR_NOTDEF);
    }

    /* If the port's connected, set the connect_port members to zero,
       removing the assembler's sense of a connection, and call
       mas_asm_cleanup_port and schedule mas_dev_disconnect_port
       device actions for both it and the port it's connected to. */
    if ( port->connected_port )
    {
        /** on the OTHER port... */
        cport = _port_hash[port->connected_port];
	if ( cport )
	{
            /* break the connection */
	    cport->connected_port = 0;

            /* remove associated structures */
            if ( cleanup_ports )
            {
                err = mas_asm_cleanup_port( cport->portnum );
                if ( err < 0 )
                {
                    masc_log_message( MAS_VERBLVL_WARNING, "asm: [warning] failure cleaning up port %d connected to %d;  continuing.", cport->portnum, portnum );
                }
            }

            /* schedule disconnect port */
            setup_dev_disconnect_port( cport );
        }
        

        /** on THIS port... */
        /* break the connection */
        port->connected_port = 0;

        /* remove associated structures */
        if ( cleanup_ports )
        {
            err = mas_asm_cleanup_port( portnum );
            if ( err < 0 )
            {
                masc_log_message( MAS_VERBLVL_WARNING, "asm: [warning] failure cleaning up port %d;  continuing.", portnum );
            }
        }
        
        /* schedule disconnect port */
        setup_dev_disconnect_port( port );
    }
    else
    {
        masc_log_message( MAS_VERBLVL_WARNING, "asm: [warning] port %d isn't connected to anything", portnum );
    }
    
    masc_exiting_log_level();
    return 0;
}

/*
 * mas_asm_connect_ports - Connect a source and a sink
 *
 * This works without specifying a dc to use in the connection.  It
 * tries to figure out which one to use by examining the two ports. 
 *
 * 1. If one port already has a dc specified, it uses that dc.
 * 2. *****TODO: *** scan for best match in the cmatrix 
 *
 */

int32
mas_asm_connect_ports(int32 source, int32 sink )
{
    struct mas_port *sourcep;
    struct mas_port *sinkp;
    struct mas_data_characteristic* dc;
    int32 retval, dc_from_source=0;

    sourcep = get_port( source );
    sinkp = get_port( sink );
    
    masc_entering_log_level( "asm: mas_asm_connect_ports()" );
    
    if ( sourcep == NULL || sinkp == NULL )
    {
        masc_log_message( MAS_VERBLVL_ERROR, "asm: [error] could not find one of the specified ports" );
        masc_exiting_log_level();
	return mas_error(MERR_NOTDEF);
    }

    /* check to make sure one of the ports isn't already connected */
    if ( sourcep->connected_port != 0 )
    {
        masc_log_message( MAS_VERBLVL_ERROR, "asm: [error] Source %d is already connected to another port!  Can't connect.", source );
        masc_exiting_log_level();
	return mas_error(MERR_INVALID);
    }

    if ( sinkp->connected_port != 0 )
    {
        masc_log_message( MAS_VERBLVL_ERROR, "asm: [error] %d is already connected to another port!  Can't connect.", sink );
        masc_exiting_log_level();
	return mas_error(MERR_INVALID);
    }

    /* see if one has a dc already */
    if (sourcep->configured_data_characteristic == 0 && sinkp->configured_data_characteristic == 0)
    {
        masc_log_message( MAS_VERBLVL_ERROR, "asm: [error] Neither port has a dc.  Can't connect." );
        masc_exiting_log_level();
        return mas_error(MERR_INVALID);
    }

    /* use the dc, pulling it off of the port */
    if (sourcep->configured_data_characteristic != 0)
    {
        dc = sourcep->configured_data_characteristic;
        sourcep->configured_data_characteristic = 0;
        dc_from_source = 1;
    }
    else
    {
        dc = sinkp->configured_data_characteristic;
        sinkp->configured_data_characteristic = 0;
        dc_from_source = 0;
    }
    
    /* try connecting with this dc */
    retval = mas_asm_connect_source_sink( source, sink, dc );

    /* if this failed, we have to restore the dc */
    if ( retval < 0 )
    {
        if ( dc_from_source )
            sourcep->configured_data_characteristic = dc;
        else
            sinkp->configured_data_characteristic = dc;
    }
    else /* don't need the dc we pulled off the port anymore. */
    {
        masc_strike_dc( dc );
        masc_rtfree( dc );
    }
    
    masc_exiting_log_level();
    
    return retval;
}

int32
mas_asm_connect_named_ports( int32 source_device, int32 sink_device, char* source_name, char* sink_name)
{
    int32 err;
    int32 source, sink;

    masc_log_message(0, "connect_named_ports '%s' on %d to '%s' on %d.", source_name, source_device, sink_name, sink_device);
    
    err = masd_get_port_by_name(source_device, source_name, &source);
    if ( err < 0 ) return err;

    err = masd_get_port_by_name(sink_device, sink_name, &sink);
    if ( err < 0 ) return err;

    return mas_asm_connect_ports( source, sink );
}

int32
dc_match_cmatrix_row(struct mas_data_characteristic* dc, struct mas_characteristic_matrix* cmatrix )
{
    int i = 0;
    int j = 0;
    int numkeys;
    int32* key_map;
    int    no_match = TRUE;
    
    if ( cmatrix != 0 )
	numkeys = cmatrix->cols;
    else numkeys = 0;

    key_map = masc_rtalloc( sizeof (int32) * numkeys ); 

    /* If the first key is "*", or there aren't any keys in the matrix,
       then this characteristic matrix will accept anything.  We can
       skip it.*/
    if ( numkeys == 0 )
        goto done;

    if ( cmatrix->keys[0][0] == '*' )
        goto done;

    /* if caller asked to be configured on characteristic with
       more keys than are in the cmatrix, then there's no match */
    if ( dc->numkeys > cmatrix->cols ) 
    {
        masc_rtfree( key_map );
        return mas_error(MERR_INVALID);
    }

    /* map the dc keys to the source keys */
    for (i=0; i<numkeys; i++)
    {
        no_match = TRUE;
        for (j=0; j<dc->numkeys; j++)
        {
            if ( strcmp( cmatrix->keys[i], dc->keys[j] ) == 0)
            {
                key_map[i] = j;
                no_match = FALSE;
            }
        }
        if ( no_match ) key_map[i] = -1;
    }
    
    /* test each source cmatrix row against the dc values */
    for (i = 0; i < cmatrix->rows; i++)
    {
        no_match = FALSE;
        for (j=0; j<numkeys; j++)
        {
            /* if the value of this source key is "accept anything"
               then this matches */
            if ( cmatrix->matrix[i][j][0] != '*' )
            {
                /* otherwise, if the strings are not equal, this whole row
                   doesn't match */
                if ( strcmp( cmatrix->matrix[i][j], dc->values[key_map[j]]) != 0 )
                {
                    no_match = TRUE;
                    break;
                }
            }
        }
        if ( !no_match ) break;  /* we have a match: the ith row. */
    }

    /* if we got here and still no match, call it. */
    if ( no_match )
    {
        masc_rtfree( key_map );
        return mas_error(MERR_INVALID);
    }
        
 done:
    masc_rtfree( key_map );

    return i; /* this is the matching row */
}

int32
mas_asm_connect_source_sink(int32 source, int32 sink, struct mas_data_characteristic* dc )
{
    struct mas_port* sourcep;
    struct mas_port* sinkp;
    int32  err;

    masc_entering_log_level( "asm: mas_asm_connect_source_sink()");
    
    sourcep = get_port( source );
    sinkp = get_port( sink );
    
    if ( sourcep == NULL || sinkp == NULL )
    {
        masc_log_message( MAS_VERBLVL_ERROR, "asm: [error] could not find one of the specified ports" );
        err = mas_error(MERR_NOTDEF);
        goto done;
    }

    /* check to make sure one of the ports isn't already connected */
    /* check to make sure one of the ports isn't already connected */
    if ( sourcep->connected_port != 0 )
    {
        masc_log_message( MAS_VERBLVL_ERROR, "asm: [error] Source %d is already connected to another port!  Can't connect.", source );
	err = mas_error(MERR_INVALID);
        goto done;
    }

    if ( sinkp->connected_port != 0 )
    {
        masc_log_message( MAS_VERBLVL_ERROR, "asm: [error] %d is already connected to another port!  Can't connect.", sink );
	err = mas_error(MERR_INVALID);
        goto done;
    }

    /** first, test the source against the supplied data
	characteristic */
    err = dc_match_cmatrix_row( dc, sourcep->characteristic_matrix );
    if ( err < 0 )
    {
        masc_log_message( MAS_VERBLVL_ERROR, "asm: [error] could not match dc to any rows in source %d's cmatrix.  dc follows:", source );
        masc_print_dc( dc );
        goto done;
    }

    /** then the sink */
    err = dc_match_cmatrix_row( dc, sinkp->characteristic_matrix );
    if ( err < 0 )
    {
        masc_log_message( MAS_VERBLVL_ERROR, "asm: [error] could not match dc to any rows in sink %d's cmatrix.  dc follows:", source );
        masc_print_dc( dc );
        goto done;
    }

    /* connect the ports */
    sourcep->connected_port = sink;
    sinkp->connected_port = source;
    
    /* copy the dc */
    sourcep->configured_data_characteristic = MAS_NEW( sourcep->configured_data_characteristic );
    sinkp->configured_data_characteristic = MAS_NEW( sinkp->configured_data_characteristic );
    err = masc_setup_dc( sourcep->configured_data_characteristic, dc->numkeys );
    if ( err < 0 ) goto done;
    err = masc_setup_dc( sinkp->configured_data_characteristic, dc->numkeys );
    if ( err < 0 ) goto done;
    err = masc_copy_dc( sourcep->configured_data_characteristic, dc );
    if ( err < 0 ) goto done;
    err = masc_copy_dc( sinkp->configured_data_characteristic, dc );
    if ( err < 0 ) goto done;

    /* schedule the mas_dev_configure_port actions */
    err = setup_dev_configure_port( sourcep );
    if ( err < 0 ) goto done;
    err = setup_dev_configure_port( sinkp );
    if ( err < 0 ) goto done;

    err = setup_sch_move_data( sourcep, sinkp );
    
 done:
    masc_exiting_log_level();
    return err;
}

/* given four ports: a source "a" and a sink "d" that are connected,
   and a sink "b" and a source "c" that are unconnected, connect b and
   c in-line such that a is connected to b and c is connected to d.
   If config is false, no mas_dev_configure_port or
   mas_dev_disconnect_port actions are scheduled for the
   port-associated devices. */

int32
mas_asm_connect_inline(int32 srca, int32 snkb, struct mas_data_characteristic* ab_dc, int32 srcc, int32 snkd, struct mas_data_characteristic* cd_dc, int config)
{
    struct mas_port *ap, *bp, *cp, *dp;
    int32 err;

    masc_entering_log_level("asm: mas_asm_connect_inline");

    /* Does this port exist? */
    ap = get_port( srca );
    bp = get_port( snkb );
    cp = get_port( srcc );
    dp = get_port( snkd );
    if ( ap == NULL || bp == NULL || cp == NULL || dp == NULL )
    {
        masc_log_message( MAS_VERBLVL_ERROR, "asm: [warning] could not find one of the specified ports" );
        masc_exiting_log_level();
	return mas_error(MERR_NOTDEF);
    }

    /* a and d MUST be connected */
    if (ap->connected_port != dp->portnum )
    {
        masc_log_message( MAS_VERBLVL_ERROR, "asm: [error] ports %d and %d are not connected", ap->portnum, dp->portnum );
        masc_exiting_log_level();
	return mas_error(MERR_INVALID);
    }
    
    /* if either specified dc is NULL, then use the current
       configured data characteristic in its place. */
    if ( ab_dc == NULL)
    {
        ab_dc = MAS_NEW( ab_dc );
        masc_setup_dc( ab_dc, ap->configured_data_characteristic->numkeys );
        masc_copy_dc( ab_dc, ap->configured_data_characteristic );
    }
    
    if ( cd_dc == NULL)
    {
        cd_dc = MAS_NEW( cd_dc );
        masc_setup_dc( cd_dc, ap->configured_data_characteristic->numkeys );
        masc_copy_dc( cd_dc, ap->configured_data_characteristic );
    }

    
    /* Nondestructively test the dc compatibility. */
    if ( config )
    {
        /* If we can reconfigure the ports, we have to test the
           requested dc's against the cmatrices of ports a and d, the
           currently connected ports. */
        err = dc_match_cmatrix_row( ab_dc, ap->characteristic_matrix );
        if ( err < 0 )
        {
            masc_log_message( MAS_VERBLVL_ERROR, "asm: [error] specified dc not compatible with source port %d", ap->portnum );
            masc_exiting_log_level();
            return mas_error(MERR_INVALID);
        }
        
        err = dc_match_cmatrix_row( cd_dc, dp->characteristic_matrix );
        if ( err < 0 )
        {
            masc_log_message( MAS_VERBLVL_ERROR, "asm: [error] specified dc not compatible with sink port %d", dp->portnum );
            masc_exiting_log_level();
            return mas_error(MERR_INVALID);
        }
    }
    else
    {
        /* If config is false, all the dc's must match OR the
           specified dc's can be NULL. */
        if ( ! masc_compare_dc( ab_dc, cd_dc ) || ! masc_compare_dc( ab_dc, ap->configured_data_characteristic ) )
        {
            masc_log_message( MAS_VERBLVL_ERROR, "asm: [error] Port configuration is disabled, but the data characteristics differ!");
            masc_log_message( MAS_VERBLVL_ERROR, "asm: [error] Cannot connect without reconfiguring the ports.");
            masc_exiting_log_level();
            return mas_error(MERR_INVALID);
        }
    }

    /* The dc's must be acceptable to ports b and c, whether configure
       is true or not. */
    err = dc_match_cmatrix_row( ab_dc, bp->characteristic_matrix );
    if ( err < 0 )
    {
        masc_log_message( MAS_VERBLVL_ERROR, "asm: [error] specified dc not compatible with sink port %d", bp->portnum );
        masc_exiting_log_level();
        return mas_error(MERR_INVALID);
    }
    
    err = dc_match_cmatrix_row( cd_dc, cp->characteristic_matrix );
    if ( err < 0 )
    {
        masc_log_message( MAS_VERBLVL_ERROR, "asm: [error] specified dc not compatible with source port %d", cp->portnum );
        masc_exiting_log_level();
        return mas_error(MERR_INVALID);
    }
        
    /*** Once we're here, we start modifying the assemblage! */
    
    if ( config )
    {
        /* I don't wanna think about this right now.
         *
         */
    
        return mas_error(MERR_NOSUPP);
    }
    else
    {
        /* Config is false; don't reconfigure assemblage. */
        ap->connected_port = bp->portnum;
        bp->connected_port = ap->portnum;
        cp->connected_port = dp->portnum;
        dp->connected_port = cp->portnum;

        /* copy the dc */
        
        if ( bp->configured_data_characteristic != NULL )
        {
            masc_strike_dc( bp->configured_data_characteristic );
            masc_rtfree( bp->configured_data_characteristic );
        }
        bp->configured_data_characteristic = ab_dc;
        
        if ( cp->configured_data_characteristic != NULL )
        {
            masc_strike_dc( cp->configured_data_characteristic );
            masc_rtfree( cp->configured_data_characteristic );
        }
        
        cp->configured_data_characteristic = cd_dc;
        
        strike_sch_move_data( ap );
        setup_dev_configure_port( bp );
        setup_dev_configure_port( cp );
        setup_sch_move_data( ap, bp );
        setup_sch_move_data( cp, dp );
    }
    
    return 0; 
}

/* given four ports: a source "a" and a sink "b" that are connected,
   and a source "c" and a sink "d" that are connected, connect a to d,
   breaking the connections to ports b and c.  If config is false, no
   mas_dev_configure_port or mas_dev_disconnect_port actions are
   scheduled for the port-associated devices. */

int32
mas_asm_disconnect_inline(int32 srca, int32 snkb, int32 srcc, int32 snkd, struct mas_data_characteristic* dc, int config)
{
    struct mas_port *ap, *bp, *cp, *dp;
    int32 err;
    
    masc_entering_log_level("asm: mas_asm_disconnect_inline");

    /* Does this port exist? */
    ap = get_port( srca );
    bp = get_port( snkb );
    cp = get_port( srcc );
    dp = get_port( snkd );
    if ( ap == NULL || bp == NULL || cp == NULL || dp == NULL )
    {
        masc_log_message( MAS_VERBLVL_ERROR, "asm: [warning] could not find one of the specified ports" );
        masc_exiting_log_level();
	return mas_error(MERR_NOTDEF);
    }

    /* a and b, c and d MUST be connected */
    if (ap->connected_port != bp->portnum )
    {
        masc_log_message( MAS_VERBLVL_ERROR, "asm: [error] ports %d and %d are not connected", ap->portnum, bp->portnum );
        masc_exiting_log_level();
	return mas_error(MERR_INVALID);
    }
    
    if (cp->connected_port != dp->portnum )
    {
        masc_log_message( MAS_VERBLVL_ERROR, "asm: [error] ports %d and %d are not connected", cp->portnum, dp->portnum );
        masc_exiting_log_level();
	return mas_error(MERR_INVALID);
    }
    
    
    /* if either specified dc is NULL, then use port a's configured
       data characteristic in its place. */
    if ( dc == NULL)
    {
        dc = MAS_NEW( dc );
        masc_setup_dc( dc, ap->configured_data_characteristic->numkeys );
        masc_copy_dc( dc, ap->configured_data_characteristic );
    }
    
    /* Nondestructively test the dc compatibility. */
    if ( config )
    {
        /* If we can reconfigure the ports, we have to test the
           requested dc's against the cmatrices of ports a and d. */
        err = dc_match_cmatrix_row( dc, ap->characteristic_matrix );
        if ( err < 0 )
        {
            masc_log_message( MAS_VERBLVL_ERROR, "asm: [error] specified dc not compatible with source port %d", ap->portnum );
            masc_exiting_log_level();
            return mas_error(MERR_INVALID);
        }
        
        err = dc_match_cmatrix_row( dc, dp->characteristic_matrix );
        if ( err < 0 )
        {
            masc_log_message( MAS_VERBLVL_ERROR, "asm: [error] specified dc not compatible with sink port %d", dp->portnum );
            masc_exiting_log_level();
            return mas_error(MERR_INVALID);
        }
    }
    else
    {
        /* If config is false, all the dc's must match OR the
           specified dc's can be NULL. */
        if ( ! masc_compare_dc( ap->configured_data_characteristic, dp->configured_data_characteristic ) || ! masc_compare_dc( dc, ap->configured_data_characteristic ) )
        {
            masc_log_message( MAS_VERBLVL_ERROR, "asm: [error] Port configuration is disabled, but the data characteristics differ!");
            masc_log_message( MAS_VERBLVL_ERROR, "asm: [error] Cannot connect without reconfiguring the ports.");
            masc_exiting_log_level();
            return mas_error(MERR_INVALID);
        }
    }

    /*** Once we're here, we start modifying the assemblage! */
    
    if ( config )
    {
        /* I don't wanna think about this right now.
         *
         */
    
        return mas_error(MERR_NOSUPP);
    }
    else
    {
        /** config is false */
        ap->connected_port = dp->portnum;
        dp->connected_port = ap->portnum;
        bp->connected_port = 0;
        cp->connected_port = 0;

        if ( bp->configured_data_characteristic != NULL )
        {
            masc_strike_dc( bp->configured_data_characteristic );
            masc_rtfree( bp->configured_data_characteristic );
            bp->configured_data_characteristic = 0;
        }
        
        if ( cp->configured_data_characteristic != NULL )
        {
            masc_strike_dc( cp->configured_data_characteristic );
            masc_rtfree( cp->configured_data_characteristic );
            cp->configured_data_characteristic = 0;
        }
        
        strike_sch_move_data( ap );
        strike_sch_move_data( cp );
        setup_dev_disconnect_port( bp );
        setup_dev_disconnect_port( cp );
        setup_sch_move_data( ap, dp );
    }
    
    return 0; 
}


int32
mas_asm_get_action_from_name(int32 device_instance, char* name, int (**retval_action)(int32, void*) )
{
    int16 action_index;
    int32 err;

    if ( ( err = mas_asm_get_action_index( device_instance, name, &action_index ) ) < 0 ) 
	return err;

    *retval_action = _device_hash[device_instance]->profile->action[action_index];

    return 0;
}

int32
mas_asm_get_action_from_index(int32 device_instance, int16 action_index, int (**retval_action)(int32, void*) )
{
    /* no error checking */
    *retval_action = _device_hash[device_instance]->profile->action[action_index];

    return 0;
}

int32
mas_asm_get_action_stats_ro(int32 device_instance, int16 action_index, struct mas_stats** retval_stats_ro )
{
    struct mas_device_profile* profile;

    if ( action_index < 0 )
        return mas_error(MERR_INVALID);

    profile = _device_hash[device_instance]->profile;

    if ( profile == 0 ) return mas_error(MERR_NULLPTR);
    *retval_stats_ro = &(profile->action_wallclock_stats[action_index]);

    return 0;
}

int32
mas_asm_get_action_index(int32 device_instance, char* name, int16* retval_action_index )
{
    int i;
    struct mas_device* device;
    
    if ( device_instance > MAX_DEVICES || device_instance < 0 ) 
	return mas_error(MERR_NOTDEF);

    if ( ( device = _device_hash[device_instance] ) == 0 ) 
	return mas_error(MERR_NOTDEF);

    for (i=0; i < device->profile->actions; i++)
    {
	if (strcmp(name, device->profile->action_names[i]) == 0)
	    break;
    }

    if (i == device->profile->actions) return mas_error(MERR_NOTDEF);
    *retval_action_index = i;

    return 0;
}

int32
mas_asm_set_event_action_index( struct mas_event* event )
{
    int32 err;
    
    if ( event == NULL )
        return mas_error(MERR_NULLPTR);

    if ( event->valid_action_index )
        return 0;

    err = mas_asm_get_action_index( event->device_instance, event->action_name, &event->action_index );
    if ( err < 0 ) return err;

    event->valid_action_index = TRUE;
    return 0;
}

/* these are temporary? */
struct mas_event* 
mas_asm_get_event_queue_head ( void )
{
    return _event_queue_head;
}

struct mas_mc_cb*
mas_asm_get_mc_cb ( void )
{
    return _mc_cb;
}

int32
mas_asm_schedule_event_struct( struct mas_event* event )
{
    event->id = next_event_id( );
    return masc_append_event( _event_queue_head, event );
}

int32
mas_asm_schedule_event_simple( int32 device_instance, char* action_name, void* predicate )
{
    struct mas_event* event;

    event = MAS_NEW( event );
    if (event == NULL) return mas_error(MERR_MEMORY);

    /* copy stuff to the event struct */
    event->device_instance = device_instance;

    /* bytecopy the action name string */
    event->action_name = masc_rtalloc(strlen(action_name) + 1);
    strcpy(event->action_name, action_name);
    
    /* don't bytecopy the predicate */
    event->predicate = predicate;
    
    /* type undefined */
    event->type = 0;

    return mas_asm_schedule_event_struct( event );
}

int32
mas_asm_schedule_event_simple_now( struct mas_event* current, int32 device_instance, char* action_name, void* predicate )
{
    struct mas_event* event;
    int32             err;

    event = MAS_NEW( event );
    if (event == NULL) return mas_error(MERR_MEMORY);

    /* copy stuff to the event struct */
    event->device_instance = device_instance;

    /* bytecopy the action name string */
    event->action_name = masc_rtalloc(strlen(action_name) + 1);
    strcpy(event->action_name, action_name);
    
    /* don't bytecopy the predicate */
    event->predicate = predicate;
    
    /* type undefined */
    event->type = 0;

    event->id = next_event_id( );
    err = masc_insert_event( current, event );
    if ( err < 0 ) return err;

    return 0;
}

int32
mas_asm_schedule_event(int32 target_device_instance, 
		       const char* action_name, void* predicate, 
		       uint32 secs, uint32 frac, int time_is_rel,
		       int priority, uint32 period, 
		       int32 num_port_dependencies, 
		       int32* port_dependencies)
{
    struct mas_event* event;

    /* for now, don't support arbitrary times */
    if ( ( secs != 0 || frac != 0 ) && !time_is_rel )
        return mas_error(MERR_NOSUPP);

    /* allocate the event struct */
    event = MAS_NEW( event );
    if (event == NULL) return mas_error(MERR_MEMORY);

    /* copy stuff to the event struct */
    event->device_instance = target_device_instance;

    /* bytecopy the action name string */
    event->action_name = masc_rtalloc(strlen(action_name) + 1);
    strcpy(event->action_name, action_name);
    
    /* don't bytecopy the predicate */
    event->predicate = predicate;
    
    /* type undefined */
    event->type = 0;
    
    /* default to the microsecond system clock */
    event->period = period;
    event->clkid = MAS_MC_SYSCLK_US;
    
    event->priority = priority;

    /******* HACK: requires time_is_rel */
    if ( (secs != 0 || frac != 0)  && time_is_rel && !period)
    {
        struct mas_ntpval ntp;
        double reltime;
 
        event->clkid = MAS_MC_SYSCLK_US; /* to make sure */
        masc_get_short_usec_ts( &event->act_time );
        ntp.secs = secs;
        ntp.frac = frac;
        masc_ntp_to_double( &ntp, &reltime );
        event->act_time += (uint32)(reltime * 1.0E6);
    }
    
    /* dependencies: don't bytecopy the dependency array */
    event->num_port_dependencies = num_port_dependencies;
    event->port_dependencies = port_dependencies;

    return mas_asm_schedule_event_struct( event );
}

int32
mas_asm_schedule_event_periodic(int32 target_device_instance, const char* action_name, void* predicate, int priority, uint32 period, int32 clkid )
{
    struct mas_event* event;

    /* allocate the event struct */
    event = MAS_NEW( event );
    if (event == NULL) return mas_error(MERR_MEMORY);

    /* copy stuff to the event struct */
    event->device_instance = target_device_instance;

    /* bytecopy the action name string */
    event->action_name = masc_rtalloc(strlen(action_name) + 1);
    strcpy(event->action_name, action_name);
    
    /* don't bytecopy the predicate */
    event->predicate = predicate;
    
    /* type undefined */
    event->type = 0;
    
    event->period = period;
    event->clkid = clkid;
    
    event->priority = priority;

    return mas_asm_schedule_event_struct( event );
}

/***************************************************************************
 * mas_asm_is_data_on_port
 *
 * arguments:
 *  1. portnum
 *
 *  Returns true if data is in the port structure.
 *
 * returns: MERR_NOTDEF if port is not defined or has no data.
 *
 ***************************************************************************/
int32
mas_asm_is_data_on_port( int32 portnum )
{
    if (portnum > MAX_PORTS || portnum < 0) 
	return mas_error(MERR_NOTDEF);

    if ( _port_hash[portnum] == 0)
	return mas_error(MERR_NOTDEF);

    if ( _port_hash[portnum]->data ) 
	return TRUE;
    
    return 0;
}

int32
mas_asm_get( char* key, struct mas_package* arg, int send_req_info, struct mas_package* r_package )
{
    char   buffer[4096];
    struct mas_package temp_pack;
    struct mas_port* port;
    struct mas_device* device;
    struct mas_device_profile* profile;
    uint8 aid;
    int32 device_instance;
    /* list of nuggets.  preserve the terminator */
    static char* nuggets[] = 
        { "list", "ports", "devices", "actions", "action_wcstat",
          "action_wcstats", "connport", "" };
    int i, j;
    int16 n=0;

    /* count the defined nuggets */
    while ( *nuggets[n] != 0 ) n++;

    i = masc_get_string_index(key, nuggets, n);

    if ( send_req_info )
    {
        masc_pushk_string( r_package, "req key", key );
        if ( arg != 0 )
            masc_pushk_payload( r_package, "req arg", arg->contents, arg->size );
        masc_pushk_int8( r_package, "top", 0 );
    }
 
    switch (i)
    {
    case 0: /*list*/
        masc_push_strings( r_package, nuggets, n );
        break;
    case 1: /*ports*/
        /* do one at a time */
        for (j=0; j<MAX_PORTS; j++)
        {
            port = _port_hash[j]; /* for clarity */
            if ( port != 0 )
            {
                masc_setup_package( &temp_pack, buffer, sizeof buffer, MASC_PACKAGE_STATIC );
                masc_pushk_string( &temp_pack, "name", port->name );
                masc_pushk_int32( &temp_pack, "portnum", port->portnum );
                masc_pushk_int32( &temp_pack, "device_instance", port->device_instance );
                masc_pushk_int16( &temp_pack, "port_type", port->port_type );
                masc_pushk_int32( &temp_pack, "connected_port", port->connected_port );
                masc_finalize_package( &temp_pack );
                masc_push_package( r_package, &temp_pack );
                masc_strike_package( &temp_pack );
            }
        }
        break;
    case 2: /*devices*/
        /* do one at a time */
        for (j=0; j<MAX_DEVICES; j++)
        {
            device = _device_hash[j]; /* for clarity */
            if ( device != 0 )
            {
                masc_setup_package( &temp_pack, buffer, sizeof buffer, MASC_PACKAGE_STATIC );
                masc_pushk_string( &temp_pack, "name", device->profile->name );
                masc_pushk_int32( &temp_pack, "instance", device->instance );
                masc_finalize_package( &temp_pack );
                masc_push_package( r_package, &temp_pack );
                masc_strike_package( &temp_pack );
            }
        }
        break;
    case 3: /*actions*/
        if ( arg == 0 ) return mas_error( MERR_INVALID );
        masc_pull_int32(arg, &device_instance);
        if ( device_instance >= MAX_DEVICES )
            return mas_error(MERR_INVALID);
        if ( _device_hash[device_instance] == 0 )
            return mas_error(MERR_INVALID);
        profile = _device_hash[device_instance]->profile;
        if ( profile == 0 ) return mas_error(MERR_INVALID);
        masc_push_strings( r_package, profile->action_names, profile->actions );
        break;
    case 4: /*action_wcstat*/
        if ( arg == 0 ) return mas_error( MERR_INVALID );
        masc_pull_int32(arg, &device_instance);
        masc_pull_uint8(arg, &aid);
        if ( device_instance >= MAX_DEVICES )
            return mas_error(MERR_INVALID);
        if ( _device_hash[device_instance] == 0 )
            return mas_error(MERR_INVALID);
        profile = _device_hash[device_instance]->profile;
        if ( profile == 0 ) return mas_error(MERR_INVALID);
        masc_pushk_uint32( r_package, "count", profile->action_wallclock_stats[aid].count );
        masc_pushk_double( r_package, "win_mean", profile->action_wallclock_stats[aid].win_mean );
        masc_pushk_uint16( r_package, "win_size", (uint16) profile->action_wallclock_stats[aid].win_size );
        masc_pushk_double( r_package, "min", profile->action_wallclock_stats[aid].min );
        masc_pushk_double( r_package, "max", profile->action_wallclock_stats[aid].max );
        break;
    case 5: /*action_wcstats*/
        if ( arg == 0 ) return mas_error( MERR_INVALID );
        masc_pull_int32(arg, &device_instance);
        if ( device_instance >= MAX_DEVICES )
            return mas_error(MERR_INVALID);
        if ( _device_hash[device_instance] == 0 )
            return mas_error(MERR_INVALID);
        profile = _device_hash[device_instance]->profile;
        if ( profile == 0 ) return mas_error(MERR_INVALID);
        {
            for ( aid=0; aid<profile->actions; aid++ )
            { 
                masc_setup_package( &temp_pack, buffer, sizeof buffer, MASC_PACKAGE_STATIC );
                masc_pushk_uint32( &temp_pack, "count", profile->action_wallclock_stats[aid].count );
                masc_pushk_double( &temp_pack, "win_mean", profile->action_wallclock_stats[aid].win_mean );
                masc_pushk_uint16( &temp_pack, "win_size", (uint16) profile->action_wallclock_stats[aid].win_size );
                masc_pushk_double( &temp_pack, "min", profile->action_wallclock_stats[aid].min );
                masc_pushk_double( &temp_pack, "max", profile->action_wallclock_stats[aid].max );
                masc_finalize_package( &temp_pack );
                masc_push_package( r_package, &temp_pack );
                masc_strike_package( &temp_pack );
            }
        }
        break;
    case 6: /*connport*/
    {
        int32 portnum;
        
        if ( arg == 0 )
        {
            masc_pushk_int32( r_package, "err", mas_error(MERR_INVALID) );
            break;
        }

        masc_pull_int32( arg, &portnum );
        
        if (portnum > MAX_PORTS || portnum < 0) 
        {
            masc_pushk_int32( r_package, "err", mas_error(MERR_NOTDEF) );
            break;
        }
        
        if ( _port_hash[portnum] == 0)
        {
            masc_pushk_int32( r_package, "err", mas_error(MERR_NOTDEF) );
            break;
        }

        masc_pushk_int32( r_package, "connport", _port_hash[portnum]->connected_port );
    }
    break;
    default:
        return mas_error(MERR_INVALID);
        break;
    }

    masc_finalize_package( r_package );
                
    return 0;
}

char** 
mas_asm_actions( void )
{
    return _actions;
}

/***********************************************************************
 * DEVICE INTERFACE
 ***********************************************************************/
/***************************************************************************
 * masd_get_data
 *
 * arguments:
 *  1. portnum
 *  2. pointer to pointer to mas_data struct
 *
 *  Retrieves the next mas_data struct from the data queue on the
 *  specified port, resetting the queue's head and clearing mas_data's
 *  "next" member.
 *
 * returns: NULLPTR error when no data present.
 *          MERR_NOTDEF if port is not defined.
 *
 ***************************************************************************/
int32
masd_get_data(int32 portnum, struct mas_data** data)
{
    if ( portnum > MAX_PORTS || portnum < 0 ) 
	return mas_error(MERR_NOTDEF);
    if (_port_hash[portnum] == 0) 
	return mas_error(MERR_NOTDEF);

    *data = _port_hash[portnum]->data; /* take from the head of
						the queue */
    if (*data == 0) return mas_error(MERR_NULLPTR);

    /* the port's data head is now the next in line */
    _port_hash[portnum]->data = (*data)->next;
    (*data)->next = 0;

    /* reset the tail if we just took it */
    if ( _port_hash[portnum]->data == 0 )
        _port_hash[portnum]->data_tail = 0;

    return 0;
}

/***************************************************************************
 * masd_post_data
 *
 * arguments:
 *  1. portnum
 *  2. pointer to mas_data struct
 *
 *  Adds the mas_data struct to the end of the specified port's data
 *  queue.
 *
 * returns: MERR_NOTDEF if port is not defined.
 *
 ***************************************************************************/
int32
masd_post_data(int32 portnum, struct mas_data* data)
{
    struct mas_data* peek;

    if ( portnum > MAX_PORTS || portnum < 0 ) 
	return mas_error(MERR_NOTDEF);

    if (_port_hash[portnum] == 0) 
	return mas_error(MERR_NOTDEF);

    peek = _port_hash[portnum]->data_tail;
    if (peek) /* if there's data in the queue, add this to the end */
    {
	peek->next = data;
    }
    else /* no data yet, just start the queue */
	_port_hash[portnum]->data = data;

    /* either way, set up the new tail - this saves mucho CPU overhead
     * searching for it.*/
    _port_hash[portnum]->data_tail = data;
            
    return 0;
}


/***************************************************************************
 * masd_get_port_by_name
 *
 * arguments:
 *  1. device instance
 *  2. string name
 *  3. pointer to int32 return value portnum
 *
 *  Uses "retval_portnum" to return the port number of a port on the
 *  given device with the given name.  If device_instance is -1, then
 *  the name is assumed to be unique over all devices and the first
 *  instance of the name in the master port list is returned.  The
 *  caller must allocate the memory to hold the value pointed to by
 *  retval_portnum. 
 *
 * returns: MERR_NOTDEF if port is not defined.
 *
 ***************************************************************************/
int32
masd_get_port_by_name(int32 device_instance, char* name, int32* retval_portnum)
{
    int i;
    int32 err = 0;

    masc_entering_log_level("masd_get_port_by_name");
    if ( device_instance > MAX_DEVICES )
        goto faildev;
    
    if ( device_instance >= 0 ) 
	if (_device_hash[device_instance] == 0)
	    goto faildev;
    
    for ( i=0; i<MAX_PORTS; i++ )
	if ( _port_hash[i])
	    if ( _port_hash[i]->device_instance
		   == device_instance || device_instance == MAS_ALL_DEVICES )
	    {
		if ( strncmp(_port_hash[i]->name,
			     name, MAX_PORT_NAME_LENGTH) == 0)
		{
		    *retval_portnum = i;
                    masc_exiting_log_level();
		    return 0;
		}
	    }

    /* didn't find match */
    err = mas_error(MERR_NOTDEF);
    masc_log_message( MAS_VERBLVL_ERROR, "ERROR: Unknown port '%s' on device %d", name, device_instance );
    *retval_portnum = 0;
    goto fail;

 faildev:
    err = mas_error(MERR_NOTDEF);
    masc_logerror( err, "Unknown device %d.", device_instance);
    
 fail:
    masc_exiting_log_level();
    return err;
}

/* THIS MAY BE BOGUS */
int32
masd_get_device_by_name(char* name, int32* retval_device_instance)
{
    int i;
    char realname[MAX_FNAME_LENGTH];
    
    get_default_device( name, realname );
    
    *retval_device_instance = -1;
    for ( i=0; i<MAX_DEVICES; i++ )
    {
	if ( _device_hash[i] != 0 )
        {
	    if (_device_hash[i]->profile != 0)
            {
		if ( strcmp( _device_hash[i]->profile->name, realname ) == 0 )
		{
		    *retval_device_instance = i;
		    break;
		}
            }
        }
    }

    if ( *retval_device_instance == -1 ) 
	return mas_error(MERR_NOTDEF);

    return 0;
}


/***************************************************************************
 * masd_get_cmatrix_from_name
 *
 *  Uses "retval_cmatrix" to return a characteristic matrix of the
 *  given device with the given name.  
 *
 * returns: MERR_NOTDEF if the device instance is not defined.
 *
 ***************************************************************************/
int32
masd_get_cmatrix_from_name(int32 device_instance, char* cmatrix_name, 
			   struct mas_characteristic_matrix** retval_cmatrix )
{
    int i;
    struct mas_device* device;
    
    if ( device_instance > MAX_DEVICES || device_instance < 0 ) 
	return mas_error(MERR_NOTDEF);

    if ( ( device = _device_hash[device_instance] ) == 0 ) 
	return mas_error(MERR_NOTDEF);

    for (i=0; i < device->profile->cmatrices; i++)
	if (strcmp(cmatrix_name, device->profile->cmatrix_names[i]) == 0)
	    break;

    if (i == device->profile->cmatrices) return mas_error(MERR_NOTDEF);
    *retval_cmatrix = &(device->profile->cmatrix[i]);

    return 0;

}

/***************************************************************************
 * masd_set_port_name
 *
 *  Sets the name of a port.
 *
 * returns: MERR_NOTDEF if the port number is not defined.
 *
 ***************************************************************************/
int32
masd_set_port_name(int32 portnum, char* name)
{
    if ( portnum > MAX_PORTS || portnum < 0 ) 
	return mas_error(MERR_NOTDEF);

    if (_port_hash[portnum] == 0) 
	return mas_error(MERR_NOTDEF);

    /* blow away old name */
    memset( _port_hash[portnum]->name, 0, MAX_PORT_NAME_LENGTH );
    strncpy(_port_hash[portnum]->name, name,
	    MAX_PORT_NAME_LENGTH);
    /* re-terminate */
    _port_hash[portnum]->name[MAX_PORT_NAME_LENGTH-1] = 0;
    
    return 0;
}

/***************************************************************************
 * masd_set_port_type
 *
 *  Sets the type of a port.
 *
 * returns: MERR_NOTDEF if the port number is not defined.
 *
 ***************************************************************************/
int32
masd_set_port_type(int32 portnum, int16 type)
{
    if ( portnum > MAX_PORTS || portnum < 0 ) 
	return mas_error(MERR_NOTDEF);

    if (_port_hash[portnum] == 0) 
	return mas_error(MERR_NOTDEF);

    _port_hash[portnum]->port_type = type;

    return 0;
}

/***************************************************************************
 * masd_set_port_cmatrix
 *
 *  Sets the characteristic matrix of a port.
 *
 * returns: MERR_NOTDEF if the port number is not defined.
 *
 ***************************************************************************/
int32
masd_set_port_cmatrix(int32 portnum, struct mas_characteristic_matrix*
		      cmatrix)
{
    if ( portnum > MAX_PORTS || portnum < 0 ) 
	return mas_error(MERR_NOTDEF);

    if (_port_hash[portnum] == 0) 
	return mas_error(MERR_NOTDEF);

    _port_hash[portnum]->characteristic_matrix = cmatrix;

    return 0;
}

int32
masd_get_port_name(int32 portnum, char **retval_name)
{
    struct mas_port* port;

    port = get_port( portnum );

    if ( port == NULL )
        return mas_error(MERR_INVALID);

    *retval_name = port->name;
    
    return 0;
}

int32
masd_get_port_type(int32 portnum, int16 *retval_type)
{
    struct mas_port* port;

    port = get_port( portnum );

    if ( port == NULL )
        return mas_error(MERR_INVALID);

    *retval_type = port->port_type;

    return 0;
}

int32
masd_get_port_cmatrix(int32 portnum, struct mas_characteristic_matrix** retval_cmatrix)
{
    struct mas_port* port;

    port = get_port( portnum );

    if ( port == NULL )
        return mas_error(MERR_INVALID);
    
    *retval_cmatrix = port->characteristic_matrix;

    return 0;
}

/***************************************************************************
 * masd_get_state
 *
 *  Retrieves the device's state pointer.  Devices can use the get/set
 *  state functions to store state information associated with the device
 *  instance number.  The caller will need to cast the void pointer to
 *  something else.
 *
 * returns: MERR_NOTDEF if the device instance number is not defined.
 *
 ***************************************************************************/
int32
masd_get_state( int32 device_instance, void** retval_state )
{
    if ( device_instance > MAX_DEVICES || device_instance < 0 ) 
	return mas_error(MERR_NOTDEF);

    if (_device_hash[device_instance] == 0) 
	return mas_error(MERR_NOTDEF);

    *retval_state = _device_hash[device_instance]->state;
    return 0;
}



/***************************************************************************
 * masd_set_state
 *
 *  Sets the device's state pointer.  Devices can use the get/set
 *  state functions to store state information associated with the device
 *  instance number.  The caller will need to cast the void pointer to
 *  something else.
 *
 * returns: MERR_NOTDEF if the device instance number is not defined.
 *
 ***************************************************************************/
int32
masd_set_state( int32 device_instance, void* state )
{
    if ( device_instance > MAX_DEVICES || device_instance < 0 ) 
	return mas_error(MERR_NOTDEF);

    if (_device_hash[device_instance] == 0) 
	return mas_error(MERR_NOTDEF);

    _device_hash[device_instance]->state = state;
    return 0;
}

int32
masd_get_data_characteristic (int32 portnum, struct mas_data_characteristic** dc )
{
    if ( portnum > MAX_PORTS || portnum < 0 ) 
	return mas_error(MERR_NOTDEF);

    if (_port_hash[portnum] == 0) 
	return mas_error(MERR_NOTDEF);

    if ( _port_hash[portnum]->configured_data_characteristic == 0 ) 
	return mas_error(MERR_INVALID);

    *dc = _port_hash[portnum]->configured_data_characteristic;

    return 0;
}


/* WARNING: this doesn't check for compatibility with the cmatrix! */
int32
masd_set_data_characteristic (int32 portnum, struct mas_data_characteristic* dc )
{
    if ( portnum > MAX_PORTS || portnum < 0 ) 
	return mas_error(MERR_NOTDEF);

    if (_port_hash[portnum] == 0) 
	return mas_error(MERR_NOTDEF);

    /* WARNING: this blows away the old DC */
    if ( _port_hash[portnum]->configured_data_characteristic != 0 )
    {
        masc_strike_dc( _port_hash[portnum]->configured_data_characteristic );
        masc_rtfree( _port_hash[portnum]->configured_data_characteristic );
    }
    
    _port_hash[portnum]->configured_data_characteristic = dc;

    return 0;
}

/***********************************************************************
 * LOCAL FUNCTIONS
 ***********************************************************************/

int32
setup_sch_move_data( struct mas_port *srcp, struct mas_port *snkp )
{
    void*  move_predicate;
    int32* port_dependencies;

    /* The predicate is source, sink.  There is one port dependency,
       the source.  The priority is DATAFLOW, and the period is 1us.
       This combination will still let the scheduler sleep even though
       the event's periodic time is really short; it's only when the
       dependency is met that it'll be triggered. */
    move_predicate = masc_rtalloc( 2 * sizeof (int32) );
    *(int32*)move_predicate = srcp->portnum;
    *((int32*)move_predicate+1) = snkp->portnum;
    port_dependencies = masc_rtalloc( sizeof (int32) );
    *port_dependencies = srcp->portnum;
    return mas_asm_schedule_event(MAS_SCH_INSTANCE, "mas_sch_move_data", move_predicate, 0, 0, 0, MAS_PRIORITY_DATAFLOW, 1, 1, port_dependencies );
}

int32
strike_sch_move_data( struct mas_port *srcp )
{
    struct mas_event *event;
    
    /* Destroy any events that depend on this port. */
    for ( event = _event_queue_head->next; event != NULL; event = event->next )
    {
	if ( event->num_port_dependencies )
	{
            if ( event->port_dependencies[0] == srcp->portnum && ( strcmp( event->action_name, "mas_sch_move_data" ) == 0 ) )
            {
                masc_log_message( MAS_VERBLVL_DEBUG, "asm: striking mas_sch_move_data on port %d", srcp->portnum );
                masc_strike_event( event );
                masc_rtfree( event );
                return 0;
            }
	}
    }

    return mas_error(MERR_INVALID);
}

int32
setup_dev_configure_port( struct mas_port *port )
{
    void *port_config_predicate;
    int32 err;
    
    /* If we have a _current_event, then schedule these actions to
       immediately follow the current action -- they have to be done
       ASAP.  If we don't, there's nothing we can do about it, just
       append the actions to the end of the queue. */
    
    port_config_predicate = masc_rtalloc( sizeof (int32) );
    *(int32*)port_config_predicate = port->portnum;
    if ( _current_event )
    {
        err = mas_asm_schedule_event_simple_now(_current_event, port->device_instance, "mas_dev_configure_port", port_config_predicate);
    }
    else
    {
        err = mas_asm_schedule_event_simple(port->device_instance, "mas_dev_configure_port", port_config_predicate);
    }

    return err;
}

int32
setup_dev_disconnect_port( struct mas_port *port )
{
    void *port_config_predicate;
    int32 err;
    
    /* If we have a _current_event, then schedule these actions to
       immediately follow the current action -- they have to be done
       ASAP.  If we don't, there's nothing we can do about it, just
       append the actions to the end of the queue. */
    
    port_config_predicate = masc_rtalloc( sizeof (int32) );
    *(int32*)port_config_predicate = port->portnum;
    if ( _current_event )
    {
        err = mas_asm_schedule_event_simple_now(_current_event, port->device_instance, "mas_dev_disconnect_port", port_config_predicate);
    }
    else
    {
        err = mas_asm_schedule_event_simple(port->device_instance, "mas_dev_disconnect_port", port_config_predicate);
    }

    return err;
}


/* name must have been allocated */
int32
generate_tracking_assemblage_name( struct mas_event* event, char* name )
{
    struct mas_device* device;

    device = get_device( event->source_device_instance );
    if ( device == 0 )
        return mas_error(MERR_INVALID);
            
    snprintf( name, MAX_ASMB_NAME_LENGTH - 1, "%s_%d_%u", device->profile->name, event->source_device_instance, event->source_device_subscript );

    return 0; 
}

void
get_libname_from_device_name( char* name, char* return_library_name)
{
    int offset = 0;

    /* assumes legal device name for now */
    strncpy(return_library_name, MAS_DEVLIB_PREFIX, MAX_FNAME_LENGTH);
    offset += strlen(MAS_DEVLIB_PREFIX);
    strncpy(return_library_name+offset, name, MAX_FNAME_LENGTH-offset);
    offset += strlen(name);
    strncpy(return_library_name+offset, MAS_DEVLIB_POSTFIX, MAX_FNAME_LENGTH-offset);
    return_library_name[MAX_FNAME_LENGTH-1] = 0;
}

int32
get_default_device( char* name, char* return_default_name )
{
    char* value;
    
    if (return_default_name == 0) return mas_error(MERR_NULLPTR);

    /* safely copy the string */
    strncpy(return_default_name, name, MAX_FNAME_LENGTH);
    return_default_name[MAX_FNAME_LENGTH-1] = 0;
    
    masc_trim_string(return_default_name); /* trim whitespace */
    
    value = masi_get_value_from_key( _default_device_map, return_default_name );
    if (value[0] != 0)
    {
	strncpy(return_default_name, value, MAX_FNAME_LENGTH);
	return_default_name[MAX_FNAME_LENGTH-1] = 0;
    }
    
    return 0;
}

struct mas_device_profile*
get_device_profile_from_name( char* device_name )
{
    struct mas_device_profile* profile;

    if ( _profile_list_head == NULL )
        return NULL;
    
    for (profile = _profile_list_head; profile != NULL; profile = profile->next )
    {
        if (strncmp(device_name, profile->name, MAX_FNAME_LENGTH) == 0)
            return profile;
    }

    return NULL;
}

struct mas_assemblage*
get_assemblage_from_name( char* name )
{
    struct mas_assemblage* assemblage;

    if ( _assemblage_list_head == NULL )
        return NULL;
    
    for (assemblage = _assemblage_list_head; assemblage != NULL; assemblage = assemblage->next )
    {
        if (strncmp(name, assemblage->name, MAX_ASMB_NAME_LENGTH-1) == 0)
            return assemblage;
    }

    return NULL;
}

int32
read_profile_symbols( struct mas_device_profile* profile)
{
    void* sym;
    int   i;
    int   errs = 0;
    int32 err;
    
    /* The symbol resolution errors are accumulated until the end of
       the function. */
    
    sym = resolve_symbol_by_name( profile->handle, "profile_action_names" ); 
    if (sym == 0) errs++;
    else profile->action_names = (char **)sym;

    sym = resolve_symbol_by_name( profile->handle, "profile_name" ); 
    if (sym == 0) errs++;
    else profile->name = (char *)sym;

    sym = resolve_symbol_by_name( profile->handle, "profile_purpose" );
    if (sym == 0) errs++;
    else profile->purpose = (char *)sym;

    sym = resolve_symbol_by_name( profile->handle,
				  "profile_description" );  
    if (sym == 0) errs++;
    else profile->description = (char *)sym;

    sym = resolve_symbol_by_name( profile->handle, "profile_license" ); 
    if (sym == 0) errs++;
    else profile->license = (char *)sym;

    sym = resolve_symbol_by_name( profile->handle, "profile_build_date" ); 
    if (sym == 0) errs++;
    else profile->build_date = (char *)sym;

    sym = resolve_symbol_by_name( profile->handle, "profile_build_host" ); 
    if (sym == 0) errs++;
    else profile->build_host = 	(char *)sym;

    sym = resolve_symbol_by_name( profile->handle, "profile_major_version" );  
    if (sym == 0) errs++;
    else profile->major_version = *(uint8 *)sym;

    sym = resolve_symbol_by_name( profile->handle, "profile_minor_version" ); 
    if (sym == 0) errs++;
    else profile->minor_version = *(uint8 *)sym;

    sym = resolve_symbol_by_name( profile->handle, "profile_teeny_version" );
    if (sym == 0) errs++;
    else profile->teeny_version = *(uint8 *)sym;

    sym = resolve_symbol_by_name( profile->handle, "profile_patchlevel" );
    if (sym == 0) errs++;
    else profile->patchlevel = *(uint8 *)sym;

    sym = resolve_symbol_by_name( profile->handle, "profile_ports" );
    if (sym == 0) errs++;
    else 
    {
	err = read_profile_ports( sym, profile );
    }

    sym = resolve_symbol_by_name( profile->handle, "profile_cmatrix_names" ); 
    if (sym == 0) errs++;
    else profile->cmatrix_names = (char **)sym;

    /* don't error if there are no clocks */
    sym = resolve_symbol_by_name( profile->handle, "profile_clock_names" ); 
    if ( sym != 0 ) profile->clock_names = (char **)sym;

    /* don't error if there is no reentrant flag */
    profile->reentrant = FALSE; /* assume false */
    sym = resolve_symbol_by_name( profile->handle, "profile_reentrant" ); 
    if ( sym != 0 ) profile->reentrant = *(char *)sym;

    /* don't error if there's no mts_acc flag */
    profile->mts_acc = FALSE; /* assume false */
    sym = resolve_symbol_by_name( profile->handle, "profile_mts_acc" ); 
    if ( sym != 0 ) profile->mts_acc = *(char *)sym;

    /* don't error if there's no ntpts_acc flag */
    profile->ntpts_acc = FALSE; /* assume false */
    sym = resolve_symbol_by_name( profile->handle, "profile_ntpts_acc" ); 
    if ( sym != 0 ) profile->ntpts_acc = *(char *)sym;


    /** count the clocks */
    i = 0;
    if ( profile->clock_names )
    {
        while ( profile->clock_names[i] && *profile->clock_names[i] != 0 ) i++;
    }
    profile->clocks = i;
    
    /** resolve each of the action names to a function pointer */
    if ( ! profile->action_names )
    {
        masc_log_message( MAS_VERBLVL_ERROR, "asm: ERROR: no actions defined in device");
        return mas_error(MERR_ERROR);
    }
    
    i = 0;
    while (profile->action_names[i++][0] != 0); /* count actions */
    profile->actions = i-1;
    
    /* allocate memory for the function pointers */
    if ( (profile->action = 
	  masc_rtalloc(profile->actions
		  * sizeof(int (*)( int32, void* ))) ) == 0 )
    {
	return mas_error(MERR_MEMORY);
    }
    
    /* allocate memory for the action stats */
    profile->action_wallclock_stats = masc_rtalloc( profile->actions * sizeof (struct mas_stats) );

    /* resolve action symbols, set function pointers, initialize
     * statistics structs. */
    for (i=0; i<profile->actions; i++)
    {
        profile->action[i] = (int (*)( int32, void* )) resolve_symbol_by_name(profile->handle, profile->action_names[i]);
	if (profile->action[i] == 0) errs++;

        masc_stats_init( &profile->action_wallclock_stats[i], MAS_ASTAT_WINDOW_SIZE, MASC_STATS_ALL );
    }
    
    /** resolve each of the cmatrix names to a structure */
    if ( profile->cmatrix_names )
    {
        i = 0;
        while (profile->cmatrix_names[i++][0] != 0);
        profile->cmatrices = i-1;
        
        /* allocate memory for the cmatrix structures */
        if ( profile->cmatrices )
        {
            if ( (profile->cmatrix = 
                  masc_rtalloc(profile->cmatrices
                               * sizeof (struct mas_characteristic_matrix) )) == 0 )
            {
                return mas_error(MERR_MEMORY);
            }
        }
        
        /* fill out the char. matrix structures */
        for (i=0; i<profile->cmatrices; i++)
        {
            sym = resolve_symbol_by_name(profile->handle, profile->cmatrix_names[i]);
            if (sym == 0) errs++;
            else
            {
                /* set the char. matrix name */
                profile->cmatrix[i].name = profile->cmatrix_names[i];
                
                /* read in the rest of the matrix from the symbol location. */
                cmatrix_symbol_to_struct(sym, &profile->cmatrix[i]);
            }
        }
    }
    else
    {
        profile->cmatrices = 0;
        profile->cmatrix = 0;
    }
    
    if (errs > 10) return mas_error(MERR_BIGERROR);
    else if (errs) return mas_error(MERR_ERROR);
    
    return 0;
}

void
destroy_profile( struct mas_device_profile* profile)
{
    int i;

    if ( profile == NULL )
        return;
    
    if ( profile->port_names )
        masc_rtfree( profile->port_names );
    if ( profile->port_types )
        masc_rtfree( profile->port_types );
    if ( profile->port_cmatrices )
        masc_rtfree( profile->port_cmatrices );
    if ( profile->action )
        masc_rtfree( profile->action );
    if ( profile->action_wallclock_stats)
    {
        for (i=0; i<profile->actions; i++)
            masc_stats_cleanup( &profile->action_wallclock_stats[i] );
        
        masc_rtfree( profile->action_wallclock_stats );
    }
    
    if ( profile->cmatrix )
        destroy_cmatrix( profile->cmatrix );

    /* don't free the list of action names - these are const in the
       loaded object code. */

    /* rewire linked list */
    if ( profile->prev )
        profile->prev->next = profile->next;
    if ( profile->next )
        profile->next->prev = profile->prev;

    masc_rtfree( profile );
}

void
destroy_cmatrix( struct mas_characteristic_matrix* cmatrix )
{
    int i;

    if ( cmatrix == 0 )
        return;

    if ( cmatrix->matrix )
    {
        /* free each row */
        for (i=0; i<cmatrix->rows; i++)
            if ( cmatrix->matrix[i] )
                masc_rtfree( cmatrix->matrix[i] );

        /* free the matrix */
        masc_rtfree( cmatrix->matrix );
    }

    /* free the structure */
    masc_rtfree( cmatrix );
}


int32
cmatrix_symbol_to_struct(void* sym, 
			 struct mas_characteristic_matrix* cmatrix)
{
    char** probe;
    int i, j;
    
    probe = sym;
    
    /* find cols */
    i=0;
    j=0;
    while (probe[j][0] != 0) j++; /* scan first row */
    cmatrix->cols = j; /* stop at terminator */

    /* find rows */
    i=0;
    j=0;
    /* use cols+1 because of the terminator */
    while(probe[i*( cmatrix->cols + 1 )][0] !=0) i++; /* scan first column */
    cmatrix->rows = i - 1; /* stop at terminator, and don't include
			      the first row. */

    /** Allocate memory for the row pointers, not including the
        terminator we don't need anymore. */
    cmatrix->matrix = masc_rtalloc( cmatrix->rows * sizeof (char**));
    if (cmatrix->matrix == 0) return mas_error(MERR_MEMORY);

    /** Allocate memory for the column pointers in each ith row
	(subtracting one for the terminator), and set the pointers to
	the char strings.  Start after the 0th row with the key names
	in it. */
    for (i=0; i<cmatrix->rows; i++)
    {
	cmatrix->matrix[i] = masc_rtalloc( cmatrix->cols * sizeof (char*)); 
	if (cmatrix->matrix[i] == 0) return mas_error(MERR_MEMORY);

	/* Set the string pointers, taking into account that the
	   symbol is pointing to a matrix with terminators we're not
	   storing here (hence the cols + 1), and that we've gotta
	   skip the first row (hence i + 1).  */
	for (j=0; j < cmatrix->cols; j++)
	    cmatrix->matrix[i][j] = 
		probe[( i + 1 ) * ( cmatrix->cols + 1 ) + j];
    }
    
    /* simple assignment, just make sure you stop reading the keys at
       the terminator */
    cmatrix->keys = probe; 
    
    return 0;
}

int32
read_profile_ports(void* sym, struct mas_device_profile* profile)
{
    char** probe;
    int i;
    int cols = 3; /* specified */
    int rows;
    
    probe = sym;
    
    /* find rows */
    i=0;
    while(probe[i*cols][0] !=0) i++; /* scan first column */
    rows = i; /* stop at terminator. */
    
    profile->ports = rows;

    if ( rows == 0 ) return 0; /* just return in null case */

    /* Allocate memory for port_* lists.  Don't strcpy() strings, just
       point to the const strings loaded with the library.  Copy the
       values of the integer array. */
    profile->port_names = masc_rtalloc( rows * sizeof (char*) );
    if ( profile->port_names == 0 ) return mas_error(MERR_MEMORY);
    profile->port_types = masc_rtalloc( rows * sizeof (int16) );
    if ( profile->port_types == 0 ) return mas_error(MERR_MEMORY);
    profile->port_cmatrices = masc_rtalloc( rows * sizeof (char*) );
    if ( profile->port_cmatrices == 0 ) return mas_error(MERR_MEMORY);
    
    /* do the assignment */
    for (i=0; i<rows; i++)
    {
	profile->port_names[i] = probe[i*cols]; 
	if ( strcmp( probe[i*cols + 1], "sink" ) == 0 )
	    profile->port_types[i] = MAS_SINK;
	if ( strcmp( probe[i*cols + 1], "source" ) == 0 )
	    profile->port_types[i] = MAS_SOURCE;
	profile->port_cmatrices[i] = probe[i*cols + 2]; 
    }
    
    return 0;
}

void
initialize_core_profiles( void )
{
    struct mas_device* device;
    struct mas_device_profile* profile;
    char** actions;
    int i, n;
    int j;

    for (j=0; j<MAS_RESERVED_INSTANCES; j++)
    {
        device = MAS_NEW( device );
        profile = MAS_NEW( profile );

        /* Perform component-specific initialization, then fall
           through to the code after the switch. */
        switch (j)
        {
        case MAS_ASM_INSTANCE:
            actions = mas_asm_actions();
            profile->name = strdup( "asm" );
            profile->description = strdup( "assembler" );
            break;
        case MAS_SCH_INSTANCE:
            actions = mas_sch_actions();
            profile->name = strdup( "sch" );
            profile->description = strdup( "scheduler" );
            break;
        case MAS_MC_INSTANCE:
            actions = mas_mc_actions();
            profile->name = strdup( "mc" );
            profile->description = strdup( "master clock" );
            break;
        default: /* we're done */
            masc_rtfree( device );
            masc_rtfree( profile );
            return;
        }

        /* count the defined actions */
        for ( n=0; *actions[n] != 0; )
            n++;
        
        /* allocate memory */
        profile->actions = n;
        /* +1 for the terminator on the names only */
        profile->action_names = masc_rtalloc( sizeof (char*) * (profile->actions+1) );
        profile->action = masc_rtalloc( sizeof (void*) * profile->actions );
        profile->action_wallclock_stats = masc_rtalloc( sizeof *profile->action_wallclock_stats * profile->actions );

        /* assign slots to device and profile */
        add_profile( profile );
        device->profile = profile;
        device->instance = j;
        _device_hash[j] = device;

        /* add action-specific information */
        for (i=0; i<profile->actions; i++)
        {
            /* set action name and null out the function pointer */
            profile->action_names[i] = strdup( actions[i] );
            profile->action[i] = 0;

            /* clear the statistics */
            masc_stats_init( &profile->action_wallclock_stats[i], MAS_ASTAT_WINDOW_SIZE, MASC_STATS_ALL );
        }
        
        /* copy the null terminator */
        profile->action_names[profile->actions] = strdup( "" );

        /* This device is in the "server-built-in" library */
        strncpy( profile->library_filename, "server-built-in", MAX_FNAME_LENGTH-1 );

        /* Make sure it doesn't get freed. */
        profile->refcount = 1;
    }

    return;
}



/**** MAS_PATH HANDLING ROUTINES ***********************************/

void
initialize_mas_path( void )
{
    char* pwd;
    char* mas_path;
    int i;

    /* clear the list */
    _mas_path_list[0][0] = 0;
    _mas_path_list_initialized = 1;
    
    pwd = getenv("PWD");
    mas_path = getenv("MAS_PATH");

    masc_entering_log_level( "asm: Initializing the device search path.");
    if (mas_path)
    {
        append_csl_to_mas_path(mas_path);
    }
    else
    {
        masc_log_message( MAS_VERBLVL_WARNING, "asm: [warning] The MAS_PATH environment variable isn't defined.");
        masc_log_message( MAS_VERBLVL_WARNING, "asm: [warning] searching default paths for devices." );
    }
    
    /* added to help find plug-ins */
    append_to_mas_path("/usr/mas/lib/mas");
    append_to_mas_path("/usr/local/mas/lib/mas");

    for (i=0; _mas_path_list[i][0] != 0; i++)
        masc_log_message( MAS_VERBLVL_DEBUG, "asm: searching for devices in %s", _mas_path_list[i] );

    masc_exiting_log_level();
}

void
append_to_mas_path( char* dir )
{
    int i=0;
    /* find the first free entry */
    while (_mas_path_list[i][0] != 0 && i<MAX_PATH_ENTRIES) i++;

    if (i < MAX_PATH_ENTRIES)
    {
	/* copy string */
	strncpy(_mas_path_list[i], dir, MAX_FNAME_LENGTH);
	_mas_path_list[i][MAX_FNAME_LENGTH-1] = 0; /* make sure its terminated */
	_mas_path_list[i+1][0] = 0; /* and terminate the list */
    }
    
    _mas_path_list[MAX_PATH_ENTRIES][0] = 0; /* if all else fails */
}

void
append_csl_to_mas_path( char* csl )
{
    char* phead;
    char* ptail;
    char  string[MAX_FNAME_LENGTH];

    phead = csl;
    ptail = csl;
    while(*phead != 0)
    {
	while(*ptail != ':' && *ptail != 0) ptail++;
	strncpy(string, phead, ptail-phead);
	string[min(MAX_FNAME_LENGTH-1, ptail-phead)] = 0; /* terminate */
	append_to_mas_path(string);
	
	if (*ptail != 0)
	    ptail++;
	phead = ptail;
    }
}
    

/*** Master port and device hash management *****************************/
struct mas_port*
get_port( int32 portnum )
{
    if ( portnum > MAX_PORTS || portnum < 0 ) 
	return NULL;

    if (_port_hash[portnum] == 0) 
	return NULL;

    /* success */
    return _port_hash[portnum];
}

struct mas_device*
get_device( int32 di )
{
    if ( di > MAX_DEVICES || di < 0 ) 
	return NULL;

    if (_device_hash[di] == 0) 
	return NULL;

    /* success */
    return _device_hash[di];
}

static int32 
find_next_free_instance( void )
{
    int spins = 0;

    while (_device_hash[_device_ctr])
    {
	if (++_device_ctr > MAX_DEVICES)
	{
	    spins++;
	    _device_ctr = MAS_RESERVED_INSTANCES;
	}
	if (spins == 2) return mas_error(MERR_ERROR);
    }
    
    return 0;
}

static int32
find_next_free_port( void )
{
    int spins = 0;

    while (_port_hash[_master_port_counter])
    {
	if (++_master_port_counter > MAX_PORTS)
	{
	    spins++;
	    _master_port_counter = 0;
	}
	if (spins == 2) return mas_error(MERR_ERROR);
    }
    
    return 0;
}

void
add_profile( struct mas_device_profile* profile )
{
    struct mas_device_profile* tail;

    profile->id = _master_device_profile_counter;
    _master_device_profile_counter++;

    /* special case: no profiles in list */
    if ( _profile_list_head == NULL )
    {
        _profile_list_head = profile;
        profile->prev = NULL;
    }
    else
    {
        /* list has head... find the tail */
        for ( tail = _profile_list_head; tail->next != NULL;  )
            tail = tail->next;

        tail->next = profile;
        profile->prev = tail;
    }
    
    profile->next = NULL;
}

/* NOTE: does not verify that the assemblage's name is unique. */
void
add_assemblage( struct mas_assemblage* assemblage )
{
    struct mas_assemblage* tail;

    assemblage->id = _master_assemblage_counter;
    _master_assemblage_counter++;

    if ( _assemblage_list_head == NULL )
    {
        _assemblage_list_head = assemblage;
        assemblage->prev = NULL;
    }
    else
    {
        /* find tail */
        for ( tail = _assemblage_list_head; tail->next != NULL;  )
            tail = tail->next;

        tail->next = assemblage;
        assemblage->prev = tail;
    }
    
    assemblage->next = NULL;
}

int32
add_device_instance_to_assemblage( int32 di, char* assemblage_name )
{
    struct mas_devlist_node *dln_tail;
    struct mas_assemblage* assemblage;
    struct mas_devlist_node* dln;

    assemblage = get_assemblage_from_name( assemblage_name );

    if ( assemblage == NULL )
        return mas_error(MERR_INVALID);

    dln = MAS_NEW( dln );
    dln->di = di;
    
    if ( assemblage->device_list_head == NULL )
    {
        /* nothing in list, set member as head */
        assemblage->device_list_head = dln;
    }
    else
    {
        /* find tail of device list */
        for ( dln_tail = assemblage->device_list_head; dln_tail->next != NULL;  )
            dln_tail = dln_tail->next;

        /* append after tail */
        dln_tail->next = dln;
    }
    
    return 0;
}

uint32
next_event_id( void )
{
    int match = TRUE;
    struct mas_event* event;

    /* loop until we make it through the event queue without finding a
       match */
    while ( match )
    {
        match = FALSE;
        _master_event_counter++; /* try the next id */

        event = _event_queue_head; /* loop over all events in the
                                      queue */
        while (event)
        {
            if ( event->id == _master_event_counter )
            {
                /* found a match, stop now and try the next id */
                match = TRUE;
                break; /* to outer loop */
            }
            event = event->next;
        }
    }

    return _master_event_counter;
}

/*** master clock DPI interface functions
 ***
 *** Here temporarily until we can figure out how to handle the
 *** assembler static stuff, including the pointer to the master clock
 *** control block. */

int32
masd_mc_val( int id, uint32* retval )
{
    return mas_mc_get_val( _mc_cb, id, retval );
}

int32
masd_mc_match_rate( int rate )
{
    /* for now, return error if we don't have the requeted rate. */
    return mas_mc_match_rate( _mc_cb, rate );
}

int32
masd_mc_rate( int id, double* retrate )
{
    return mas_mc_get_rate( _mc_cb, id, retrate );
}

int32
masd_error_response( int32 reaction, int32 err )
{
    struct mas_package package;
    
    masc_setup_package( &package, NULL, 0, MASC_PACKAGE_NOFREE );
    masc_pushk_int32( &package, "err", err );
    masc_finalize_package( &package );
    masd_reaction_queue_response( reaction, package.contents, package.size );
    masc_strike_package( &package );

    return 0;
}

/*** signal handling DPI interface **/

int32
masd_signal_action( int signum, int handle, int32 di, char *action )
{
    struct mas_event* event;
    int32 err = 0;

    err = catch_signal( signum, handle );
    if ( err < 0 ) return err;
    
    if ( handle == MAS_SIGHNDL_MAS )
    {
        /* allocate the event struct */
        event = MAS_NEW( event );
        if (event == NULL) return mas_error(MERR_MEMORY);
        
        event->device_instance = di;

        /* bytecopy the action name string */
        event->action_name = masc_rtalloc(strlen(action) + 1);
        strcpy(event->action_name, action);
    
        event->period = 1;
        event->clkid = MAS_MC_SYSCLK_US;

        /* +1 because signal_dep == 0 means "no dependency". */
        event->signal_dep = signum + 1; 

        err = mas_asm_schedule_event_struct( event );
    }

    return err;
}

