/* -*- mode: C; tab-width:8; c-basic-offset:8 -*- * vi:set ts=8: * * al_ext.c * * Defines the mojo for built-in or dlopened extensions. */ #include "al_siteconfig.h" #include #include #include "al_main.h" #include "al_types.h" #include "al_ext.h" #include "al_error.h" #include "al_debug.h" #include "mutex/mutexlib.h" #ifndef NODLOPEN #include #endif #include #include #include #define _AL_EXT_NAMELEN 40 #define MAX_EXT_LIBS 64 /* locking macros */ #define _alUnlockExtension() FL_alUnlockExtension(__FILE__, __LINE__) #define _alLockExtension() FL_alLockExtension(__FILE__, __LINE__) /* locking funcs */ static void FL_alLockExtension(const char *fn, int ln); static void FL_alUnlockExtension(const char *fn, int ln); /* data structure storing extension library fini functions */ static struct { void (*pool[MAX_EXT_LIBS])(void); int index; } FiniFunc; /* data structure storing extension functions */ typedef struct _enode_t { ALubyte name[_AL_EXT_NAMELEN+1]; void *addr; struct _enode_t *left; struct _enode_t *right; } enode_t; static enode_t *etree = NULL; static MutexID ext_mutex = NULL; /* static function prototypes */ static enode_t *add_node(enode_t *treenode, const ALubyte *name, void *addr); static enode_t *get_node(enode_t *treenode, const ALubyte *name); static void _alDestroyExtension(void *extp); static void tree_free(enode_t *treehead, void (*ff)(void *)); static enode_t *new_ext(const ALubyte *name, void *addr); #ifndef NODLOPEN /* following functions unneeded in we don't have dlopen */ /* function to manage ext */ static void _alExtPushFiniFunc(void (*func)(void)); #endif /* NODLOPEN */ /* * On the level of the specification, "Extension" is a * group of functions and/or tokens that express a particular * extended functionality of the API. * * In terms of this implementation 'Extension' refers * to particular function that is registered in the * dictionary for fast GetProcAddress(). A simple dlsym() * is not possible because of the Linux implementation's * dynamic plugin system. * * Also in terms of the implementation, 'Extension Group' * refers to an extension in the specification sense--a name * that surrounds a group of functions and/or tokens. It * is possible to use a simple linked list for this, and * IsExtensionPresent() simply traverses this list. */ #define _AL_EXT_GROUPLEN 256 typedef struct _egroup_node_t { ALubyte name[_AL_EXT_GROUPLEN+1]; struct _egroup_node_t* next; } egroup_node_t; /* linked list of exciting extension group names */ static egroup_node_t* egroup_list = NULL; /* * Return a malloc()'ed string containing all the extensions. */ ALboolean _alGetExtensionStrings( ALubyte* buffer, int size ) { egroup_node_t* group = egroup_list; if( size < 1 ) { return AL_FALSE; } buffer[0] = '\0'; while( group ) { int length = strlen( (char*) group->name ) + 1; if( length < size ) { strcat( (char*) buffer, (char*) group->name ); strcat( (char*) buffer, " " ); } else { break; } size -= length; group = group->next; } return AL_TRUE; } /* * Add an extension group (see above) to the current list of extensions. */ ALboolean _alRegisterExtensionGroup( const ALubyte* gname ) { egroup_node_t* group; if( gname == NULL ) { return AL_FALSE; } group = (egroup_node_t*) malloc( sizeof( *group ) ); if( group == NULL ) { return AL_FALSE; } strncpy( (char*) group->name, (const char*) gname, _AL_EXT_GROUPLEN ); group->next = egroup_list; egroup_list = group; return AL_TRUE; } /* * Free the linked list. */ void _alDestroyExtensionGroups( void ) { egroup_node_t* group = egroup_list; while( group ) { egroup_node_t* temp = group->next; free( group ); group = temp; } egroup_list = NULL; } /* * Returns TRUE is gname is a supported extension, FALSE otherwise. */ ALboolean alIsExtensionPresent( const ALubyte* gname ) { egroup_node_t* group = egroup_list; while( group ) { if( strncmp( (const char*) group->name, (const char*) gname, _AL_EXT_GROUPLEN ) == 0 ) { return AL_TRUE; } group = group->next; } return AL_FALSE; } /** * Extension support. * Obtain the address of a function (usually an extension) * with the name fname. All addresses are context-independent. * */ void *alGetProcAddress(const ALubyte *fname) { enode_t *retpair; retpair = get_node(etree, fname); if(retpair == NULL) { /* * Unknown extension. Return NULL. */ return NULL; } debug(ALD_EXT, __FILE__, __LINE__, "alGetProcAddress returning %s @ %p", retpair->name, retpair->addr); return retpair->addr; } /* * _alRegisterExtension is called to register an extension in our tree. * extensions are not via GetProcAddress until they have been registered. */ ALboolean _alRegisterExtension(const ALubyte *name, void *addr) { enode_t *temp; _alLockExtension(); temp = add_node(etree, name, addr); if(temp == NULL) { _alUnlockExtension(); debug(ALD_EXT, __FILE__, __LINE__, "could not add extension %s", name); return AL_FALSE; } _alUnlockExtension(); etree = temp; debug(ALD_EXT, __FILE__, __LINE__, "registered %s at %p", name, addr); return AL_TRUE; } static void _alDestroyExtension(void *extp) { free(extp); return; } /* * _alInitExtensions * * initialize extensions bookkeeping structures. * * Not much to do here, since the structures are built * via the register calls. So just init the mutex. */ ALboolean _alInitExtensions(void) { if(ext_mutex == NULL) { ext_mutex = mlCreateMutex(); if(ext_mutex == NULL) { return AL_FALSE; } } return AL_TRUE; } /* * Destroy extension book keeping structures. */ void _alDestroyExtensions(void) { int i; tree_free(etree, _alDestroyExtension); mlDestroyMutex(ext_mutex); etree = NULL; ext_mutex = NULL; for(i = FiniFunc.index - 1; i >= 0; i--) { FiniFunc.pool[i](); /* call fini funcs */ FiniFunc.index--; } return; } /* * add a function with name "name" and address "addr" to the tree * with root at treehead. * * assumes locked extensions */ static enode_t *add_node(enode_t *treehead, const ALubyte *name, void *addr) { int i; enode_t *retval; if((addr == NULL) || (name == NULL)) { return NULL; } if(treehead == NULL) { retval = new_ext(name, addr); if(retval == NULL) { return NULL; } return retval; } i = ustrncmp(name, treehead->name, _AL_EXT_NAMELEN); if(i < 0) { treehead->left = add_node(treehead->left, name, addr); } if(i == 0) { return treehead; } if(i > 0) { treehead->right = add_node(treehead->right, name, addr); } return treehead; } static enode_t *new_ext(const ALubyte *name, void *addr) { enode_t *retval = malloc(sizeof *retval); if(retval == NULL) { return NULL; } ustrncpy(retval->name, name, _AL_EXT_NAMELEN); retval->addr = addr; retval->left = NULL; retval->right = NULL; return retval; } /* * Destroy the tree with root at treehead, calling ff on each * data item. */ static void tree_free(enode_t *treehead, void (*ff)(void *)) { enode_t *temp; if(treehead == NULL) { return; } if(treehead->left) { tree_free(treehead->left, ff); } temp = treehead->right; free(treehead); tree_free(temp, ff); return; } /* * Retrieve enode_t pointer for extension with name "name" from * tree with root "treehead", or NULL if not found. */ static enode_t *get_node(enode_t *treehead, const ALubyte *name) { int i; if((name == NULL) || (treehead == NULL)) { return NULL; } i = ustrncmp(name, treehead->name, _AL_EXT_NAMELEN); if(i < 0) { return get_node(treehead->left, name); } if(i > 0) { return get_node(treehead->right, name); } return treehead; } void alLokiTest(UNUSED(void *dummy)) { fprintf(stderr, "LokiTest\n"); return; } #ifdef NODLOPEN ALboolean _alLoadDL(UNUSED(const char *fname)) { return AL_FALSE; } #else /* * FIXME: some sort of locking is in order. * * FIXME: stick the fini_func function somewhere so we * can actually call it. */ ALboolean _alLoadDL(const char *fname) { void *handle; AL_extension *ext_table; static void (*init_func)(void); static void (*fini_func)(void); int i; handle = dlopen(fname, RTLD_GLOBAL | RTLD_NOW); if(handle == NULL) { debug(ALD_EXT, __FILE__, __LINE__, "Could not load %s:\n\t%s", fname, dlerror()); return AL_FALSE; } ext_table = dlsym(handle, LAL_EXT_TABLE_STR); if(ext_table == NULL) { debug(ALD_EXT, __FILE__, __LINE__, "%s has no extension table.", fname); return AL_FALSE; } /* FIXME: need to add code to call fini_func */ init_func = (void (*)(void)) dlsym(handle, LAL_EXT_INIT_STR); fini_func = (void (*)(void)) dlsym(handle, LAL_EXT_FINI_STR); if(init_func != NULL) { /* initialize library */ init_func(); } for(i = 0; (ext_table[i].name != NULL) && (ext_table[i].addr != NULL); i++) { _alRegisterExtension(ext_table[i].name, ext_table[i].addr); } if(fini_func != NULL) { _alExtPushFiniFunc(fini_func); } return AL_TRUE; } #endif /* * Not super-well tested. * * FIXME: set error? Probably not. */ ALboolean lal_addTimeFilter(const char *name, time_filter *addr) { AL_context *cc; time_filter_set *tfs; int i; int scmp; /* check args */ if((name == NULL) || (addr == NULL)) { return AL_FALSE; } _alcDCLockContext(); cc = _alcDCGetContext(); if(cc == NULL) { _alcDCUnlockContext(); return AL_FALSE; } tfs = cc->time_filters; for(i = 0; (i < _ALC_MAX_FILTERS) && (tfs->filter != NULL); i++) { scmp = strncmp(tfs[i].name, name, _ALF_MAX_NAME); if(scmp == 0) { /* overwrite existing filter */ tfs[i].filter = addr; _alcDCUnlockContext(); return AL_TRUE; } } if(i == _ALC_MAX_FILTERS) { /* no room for new filter */ _alcDCUnlockContext(); return AL_FALSE; } /* add new filter */ strncpy(tfs[i].name, name, _ALF_MAX_NAME); tfs[i].filter = addr; _alcDCUnlockContext(); return AL_TRUE; } /* * Not super-well tested. */ ALboolean lal_addFreqFilter(const char *name, freq_filter *addr) { AL_context *cc; freq_filter_set *fffs; int i; int scmp; /* check args */ if((name == NULL) || (addr == NULL)) { return AL_FALSE; } _alcDCLockContext(); cc = _alcDCGetContext(); if(cc == NULL) { _alcDCUnlockContext(); return AL_FALSE; } fffs = cc->freq_filters; for(i = 0; (i < _ALC_MAX_FILTERS) && (fffs->filter != NULL); i++) { scmp = strncmp(fffs[i].name, name, _ALF_MAX_NAME); if(scmp == 0) { /* overwrite existing filter */ fffs[i].filter = addr; _alcDCUnlockContext(); return AL_TRUE; } } if(i == _ALC_MAX_FILTERS) { /* no room for new filter */ _alcDCUnlockContext(); return AL_FALSE; } /* add new filter */ strncpy(fffs[i].name, name, _ALF_MAX_NAME); fffs[i].filter = addr; _alcDCUnlockContext(); return AL_TRUE; } static void FL_alLockExtension(UNUSED(const char *fn), UNUSED(int ln)) { _alLockPrintf("_alLockExtension", fn, ln); if(ext_mutex == NULL) { /* * Sigh. Extensions are loaded from config, so they * need to have locks even before _alMain is called, * so InitExtension may not be called at this point. */ ext_mutex = mlCreateMutex(); } mlLockMutex(ext_mutex); return; } static void FL_alUnlockExtension(UNUSED(const char *fn), UNUSED(int ln)) { _alLockPrintf("_alUnlockExtension", fn, ln); mlUnlockMutex(ext_mutex); return; } #ifndef NODLOPEN /* avoid errors on platforms that don't support dlopen */ /* * Save fini func to call on dlclose time */ static void _alExtPushFiniFunc(void (*func)(void)) { if(FiniFunc.index >= MAX_EXT_LIBS) { return; } FiniFunc.pool[FiniFunc.index] = func; FiniFunc.index++; return; } #endif /* NODLOPEN */ /** * Extension support. * Obtain the integer value of an enumeration (usually an extension) * with the name ename. * * FIXME: kludgey */ ALenum alGetEnumValue(const ALubyte *ename) { if(ustrcmp("AL_INVALID", ename) == 0) { return AL_INVALID; } if(ustrcmp("AL_FALSE", ename) == 0) { return AL_FALSE; } if(ustrcmp("AL_TRUE", ename) == 0) { return AL_TRUE; } if(ustrcmp("AL_SOURCE_TYPE", ename) == 0) { return AL_SOURCE_TYPE; } if(ustrcmp("AL_SOURCE_RELATIVE", ename) == 0) { return AL_SOURCE_RELATIVE; } if(ustrcmp("AL_STREAMING", ename) == 0) { return AL_STREAMING; } if(ustrcmp("AL_CONE_INNER_ANGLE", ename) == 0) { return AL_CONE_INNER_ANGLE; } if(ustrcmp("AL_CONE_OUTER_ANGLE", ename) == 0) { return AL_CONE_OUTER_ANGLE; } if(ustrcmp("AL_PITCH", ename) == 0) { return AL_PITCH; } if(ustrcmp("AL_POSITION", ename) == 0) { return AL_POSITION; } if(ustrcmp("AL_DIRECTION", ename) == 0) { return AL_DIRECTION; } if(ustrcmp("AL_VELOCITY", ename) == 0) { return AL_VELOCITY; } if(ustrcmp("AL_LOOPING", ename) == 0) { return AL_SOURCE_LOOPING; } if(ustrcmp("AL_BUFFER", ename) == 0) { return AL_BUFFER; } if(ustrcmp("AL_GAIN", ename) == 0) { return AL_GAIN; } if(ustrcmp("AL_GAIN_LINEAR", ename) == 0) { return AL_GAIN_LINEAR; } if(ustrcmp("AL_BYTE_LOKI", ename) == 0) { return AL_BYTE_LOKI; } if(ustrcmp("AL_MIN_GAIN", ename) == 0) { return AL_MIN_GAIN; } if(ustrcmp("AL_MAX_GAIN", ename) == 0) { return AL_MAX_GAIN; } if(ustrcmp("AL_PLAY_COUNT", ename) == 0) { return AL_PLAY_COUNT; } if(ustrcmp("AL_BUFFERS_QUEUED", ename) == 0) { return AL_BUFFERS_QUEUED; } if(ustrcmp("AL_BUFFERS_USED", ename) == 0) { return AL_BUFFERS_USED; } if(ustrcmp("AL_ORIENTATION", ename) == 0) { return AL_ORIENTATION; } if(ustrcmp("AL_SOURCE_STATE", ename) == 0) { return AL_SOURCE_STATE; } if(ustrcmp("AL_INITIAL", ename) == 0) { return AL_INITIAL; } if(ustrcmp("AL_PLAYING", ename) == 0) { return AL_PLAYING; } if(ustrcmp("AL_PAUSED", ename) == 0) { return AL_PAUSED; } if(ustrcmp("AL_STOPPED", ename) == 0) { return AL_STOPPED; } if(ustrcmp("AL_FORMAT_MONO8", ename) == 0) { return AL_FORMAT_MONO8; } if(ustrcmp("AL_FORMAT_MONO16", ename) == 0) { return AL_FORMAT_MONO16; } if(ustrcmp("AL_FORMAT_STEREO8", ename) == 0) { return AL_FORMAT_STEREO8; } if(ustrcmp("AL_FORMAT_STEREO16", ename) == 0) { return AL_FORMAT_STEREO16; } if(ustrcmp("AL_FREQUENCY", ename) == 0) { return AL_FREQUENCY; } if(ustrcmp("AL_BITS", ename) == 0) { return AL_BITS; } if(ustrcmp("AL_CHANNELS", ename) == 0) { return AL_CHANNELS; } if(ustrcmp("AL_SIZE", ename) == 0) { return AL_SIZE; } if(ustrcmp("AL_NO_ERROR", ename) == 0) { return AL_NO_ERROR; } if(ustrcmp("AL_INVALID_NAME", ename) == 0) { return AL_INVALID_NAME; } if(ustrcmp("AL_ILLEGAL_ENUM", ename) == 0) { return AL_ILLEGAL_ENUM; } if(ustrcmp("AL_INVALID_VALUE", ename) == 0) { return AL_INVALID_VALUE; } if(ustrcmp("AL_ILLEGAL_COMMAND", ename) == 0) { return AL_ILLEGAL_COMMAND; } if(ustrcmp("AL_OUT_OF_MEMORY", ename) == 0) { return AL_OUT_OF_MEMORY; } if(ustrcmp("AL_VENDOR", ename) == 0) { return AL_VENDOR; } if(ustrcmp("AL_VERSION", ename) == 0) { return AL_VERSION; } if(ustrcmp("AL_RENDERER", ename) == 0) { return AL_RENDERER; } if(ustrcmp("AL_EXTENSIONS", ename) == 0) { return AL_EXTENSIONS; } if(ustrcmp("AL_DOPPLER_SCALE", ename) == 0) { return AL_DOPPLER_SCALE; } if(ustrcmp("AL_DOPPLER_SHIFT", ename) == 0) { return AL_DOPPLER_SHIFT; } if(ustrcmp("AL_DISTANCE_SCALE", ename) == 0) { return AL_DISTANCE_SCALE; } if(ustrcmp("AL_DISTANCE_ATTENUATION", ename) == 0) { return AL_DISTANCE_ATTENUATION; } if(ustrcmp("AL_PROPAGATION_SPEED", ename) == 0) { return AL_PROPAGATION_SPEED; } if(ustrcmp("AL_ENV_ROOM_IASIG", ename) == 0) { return AL_ENV_ROOM_IASIG; } if(ustrcmp("AL_ENV_ROOM_HIGH_FREQUENCY_IASIG", ename) == 0) { return AL_ENV_ROOM_HIGH_FREQUENCY_IASIG; } if(ustrcmp("AL_ENY_ROOM_ROLLOFF_FACTOR_IASIG", ename) == 0) { return AL_ENY_ROOM_ROLLOFF_FACTOR_IASIG; } if(ustrcmp("AL_ENV_DECAY_TIME_IASIG", ename) == 0) { return AL_ENV_DECAY_TIME_IASIG; } if(ustrcmp("AL_ENV_DECAY_HIGH_FREQUENCY_RATIO_IASIG", ename) == 0) { return AL_ENV_DECAY_HIGH_FREQUENCY_RATIO_IASIG; } if(ustrcmp("AL_ENV_REFLECTIONS_IASIG", ename) == 0) { return AL_ENV_REFLECTIONS_IASIG; } if(ustrcmp("AL_ENV_REFLECTIONS_DELAY_IASIG", ename) == 0) { return AL_ENV_REFLECTIONS_DELAY_IASIG; } if(ustrcmp("AL_ENV_REVERB_IASIG", ename) == 0) { return AL_ENV_REVERB_IASIG; } if(ustrcmp("AL_ENV_REVERB_DELAY_IASIG", ename) == 0) { return AL_ENV_REVERB_DELAY_IASIG; } if(ustrcmp("AL_ENV_DIFFUSION_IASIG", ename) == 0) { return AL_ENV_DIFFUSION_IASIG; } if(ustrcmp("AL_ENV_DENSITY_IASIG", ename) == 0) { return AL_ENV_DENSITY_IASIG; } if(ustrcmp("AL_ENV_HIGH_FREQUENCY_REFERENCE_IASIG", ename) == 0) { return AL_ENV_HIGH_FREQUENCY_REFERENCE_IASIG; } return 0; }