blob: c789a6217bd58022bb07bc312fb95639c5af6856 [file] [log] [blame]
/*
* target_ruby.c
*
* Target language specific functions for openwsman swig plugins
*
* Here: Ruby
*/
/*****************************************************************************
* Copyright (C) 2008 Novell Inc. All rights reserved.
* Copyright (C) 2008 SUSE Linux Products GmbH. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* - Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* - Neither the name of Novell Inc. nor of SUSE Linux Products GmbH nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS IS''
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL Novell Inc. OR SUSE Linux Products GmbH OR
* THE CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
* OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/
/* load <RB_PLUGIN_FILE>.rb */
#define PLUGIN_FILE "openwsmanplugin"
/* expect 'module <RB_PLUGIN_MODULE>' inside */
#define PLUGIN_MODULE "Openwsman"
/*
* load_module
* separate function for rb_require so it can be wrapped into rb_protect()
*/
static VALUE
load_module()
{
ruby_script(PLUGIN_FILE);
return rb_require(PLUGIN_FILE);
}
/*
* create_plugin (called from rb_protect)
* load Ruby provider and create plugin instance
*
*/
static VALUE
create_plugin(VALUE args)
{
return rb_funcall2(_TARGET_MODULE, rb_intern("create_plugin"), 0, NULL);
}
/*
* call_plugin
* call function of plugin instance
*
* I args: pointer to array of at least 3 values
* args[0] -> (VALUE) instance
* args[1] -> (VALUE) id of function
* args[2] -> (int) number of arguments
* args[3...n] -> (VALUE) arguments
*/
static VALUE
call_plugin(VALUE args)
{
VALUE *values = (VALUE *)args;
return rb_funcall3(values[0], values[1], (int)values[2], values+3);
}
/*
* get Ruby exception trace -> char *
*
*/
#define TB_ERROR(str) {tbstr = str; goto cleanup;}
static char *
get_exc_trace()
{
VALUE exception = rb_gv_get("$!"); /* get last exception */
VALUE reason = rb_funcall(exception, rb_intern("to_s"), 0 );
VALUE trace = rb_gv_get("$@"); /* get last exception trace */
VALUE backtrace = rb_funcall(trace, rb_intern("join"), 1, rb_str_new("\n\t", 2));
return fmtstr("%s\n\t%s", StringValuePtr(reason), StringValuePtr(backtrace));
}
/*
* Global Ruby initializer
* loads the Ruby interpreter
* init threads
*/
static int
RbGlobalInitialize( )
{
int error;
if (_TARGET_INIT)
{
return 0;
}
_TARGET_INIT=1;//true
debug("Ruby: Loading");
ruby_init();
ruby_init_loadpath();
ruby_script(PLUGIN_FILE);
extern void SWIG_init();
SWIG_init();
/* load module */
rb_protect(load_module, Qnil, &error);
if (error)
{
char *trace = get_exc_trace();
error("Ruby: import '%s' failed: %s", PLUGIN_FILE, trace);
return -1;
}
_TARGET_MODULE = rb_const_get(rb_cModule, rb_intern(PLUGIN_MODULE));
if (NIL_P(_TARGET_MODULE))
{
error("Ruby: import '%s' doesn't define module '%s'", PLUGIN_MODULE);
return -1;
}
debug("RbGlobalInitialize() succeeded -> module %s @ %p", PLUGIN_MODULE, _TARGET_MODULE);
return 0;
}
/*---------------------------------------------------------------*/
/*
* local (per plugin) Ruby initializer
* keeps track of reference count
*
* self: handle (from dlopen)
* data: user data return
*/
static int
TargetInitialize( void *self, void **data )
{
VALUE args[2];
int error;
debug("TargetInitialize(Ruby)");
/* Set _PLUGIN_INIT, protected by _PLUGIN_INIT_MUTEX
* so we call ruby_finalize() only once.
*/
if (pthread_mutex_lock(&_PLUGIN_INIT_MUTEX))
{
perror("Can't lock _PLUGIN_INIT_MUTEX");
abort();
}
error = RbGlobalInitialize( );
pthread_mutex_unlock(&_PLUGIN_INIT_MUTEX);
if (error != 0)
{
goto exit;
}
debug("TargetInitialize(Ruby) called");
*data = (void *)rb_protect(create_plugin, (VALUE)args, &error);
if (error)
{
char *trace = get_exc_trace();
error("Ruby: FAILED creating:", trace);
}
debug("Created plugin: klass @ %p", *data);
exit:
debug("Initialize() %s", (error == 0)? "succeeded":"failed");
return error;
}
/*
* TargetCall
* Call function 'opname' with nargs arguments within instance
*
* doc: in_doc from context, needed for fault generation
* instance: module
* opname: name of method to call
* nargs: number of arguments
* ...: arguments as varargs
*
*/
static int
TargetCall(WsXmlDocH doc, VALUE instance, const char* opname, int nargs, ...)
{
int i;
VALUE *args, result, op = rb_intern(opname);
va_list vargs;
WsmanStatus status;
wsman_status_init(&status);
debug("TargetCall(Ruby): %p.%s", (void *)instance, opname);
/* add instance, op and nargs to the args array, so rb_protect can be called */
nargs += 3;
args = (VALUE *)malloc(nargs * sizeof(VALUE));
if (args == NULL)
{
error("Out of memory");
abort();
}
args[0] = instance;
args[1] = op;
args[2] = (VALUE)(nargs-3);
if (nargs > 3)
{
va_start(vargs, nargs);
for (i = 3; i < nargs; ++i)
{
args[i] = va_arg(vargs, VALUE);
}
va_end(vargs);
}
/* call the Ruby function
* possible results:
* i nonzero: Exception raised
* result == nil: not (or badly) implemented
* result == true: success
* result == Array: pair of CMPIStatus rc(int) and msg(string)
*/
result = rb_protect(call_plugin, (VALUE)args, &i);
free( args );
if (i) /* exception ? */
{
char *trace = get_exc_trace();
status.fault_msg = fmtstr("Ruby: calling '%s' failed: %s", opname, trace);
status.fault_code = WSMAN_INTERNAL_ERROR;
status.fault_detail_code = 0;
error("%s", status.fault_msg);
return 1;
}
if (NIL_P(result)) /* not or wrongly implemented */
{
status.fault_code = WSA_ENDPOINT_UNAVAILABLE;
status.fault_detail_code = 0;
return 1;
}
if (result != Qtrue)
{
int len;
VALUE resulta = rb_check_array_type(result);
if (NIL_P(resulta))
{
status.fault_msg = fmtstr("Ruby: calling '%s' returned unknown result", opname);
status.fault_code = WSMAN_INTERNAL_ERROR;
status.fault_detail_code = 0;
return 1;
}
len = RARRAY_LEN(RARRAY(resulta));
if (len > 0)
{
VALUE code = rb_ary_entry(resulta, 0);
if (!FIXNUM_P(code))
{
status.fault_msg = fmtstr("Ruby: calling '%s' returned non-numeric code", opname);
status.fault_code = WSMAN_INTERNAL_ERROR;
status.fault_detail_code = 0;
return 1;
}
status.fault_code = FIX2LONG(code);
}
if (len > 1)
{
VALUE detail = rb_ary_entry(resulta, 1);
if (!FIXNUM_P(detail))
{
status.fault_msg = fmtstr("Ruby: calling '%s' returned non-numeric detail", opname);
status.fault_code = WSMAN_INTERNAL_ERROR;
status.fault_detail_code = 0;
return 1;
}
status.fault_detail_code = FIX2LONG(detail);
}
if (len > 2)
{
VALUE str = rb_ary_entry(resulta, 2);
status.fault_msg = StringValuePtr(str);
}
wsman_generate_fault( doc, status.fault_code, status.fault_detail_code, status.fault_msg );
}
/* all is fine */
return 0;
}
/*
* TargetCleanup
*/
static void
TargetCleanup( void *self, void *data )
{
debug("TargetCleanup(Ruby)");
ruby_finalize();
_TARGET_MODULE = Qnil;
return;
}
static VALUE
call_namespaces(VALUE klass)
{
return rb_funcall( klass, rb_intern( "namespaces" ), 0 );
}
/*
* TargetEndpoints
*/
static list_t *
TargetEndpoints( void *self, void *data )
{
int error;
VALUE klass = (VALUE)data;
VALUE rbnamespaces, ary;
debug("TargetEndpoints(Ruby), data %p, klass %p", data, klass);
/*
* Get namespaces
*/
list_t *namespaces = list_create(LISTCOUNT_T_MAX);
debug("TargetEndpoints(Ruby), calling namespaces");
rbnamespaces = rb_protect(call_namespaces, klass, &error);
if (error) {
char *trace = get_exc_trace();
error("Ruby: 'namespaces' failed: %s", PLUGIN_FILE, trace);
return NULL;
}
debug("TargetEndpoints(Ruby), called namespaces: %p", rbnamespaces);
ary = rb_check_array_type( rbnamespaces );
if (NIL_P(ary)) {
rb_raise( rb_eArgError, "namespaces is not array");
}
int len = RARRAY_LEN(RARRAY(ary));
if (len <= 0) {
rb_raise( rb_eArgError, "namespaces returned array with %d elements", len);
}
int i;
for (i = 0; i < len; ++i) {
lnode_t *node;
VALUE elem = RARRAY_PTR(RARRAY(ary))[i];
VALUE pair = rb_check_array_type( elem );
if (NIL_P(pair)) {
rb_raise( rb_eArgError, "namespaces must return array of arrays");
}
if (RARRAY_LEN(RARRAY(pair)) != 2) {
rb_raise( rb_eArgError, "namespaces must return array of ['<namespace>','<class_prefix>']");
}
WsSupportedNamespaces *ns = (WsSupportedNamespaces *)u_malloc(sizeof(WsSupportedNamespaces));
ns->ns = StringValuePtr( RARRAY_PTR(RARRAY(pair))[0] );
ns->class_prefix = StringValuePtr( RARRAY_PTR(RARRAY(pair))[1] );
node = lnode_create(ns);
list_append(namespaces, node);
}
return namespaces;
}