cmpi-bindings 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,462 @@
1
+ /*
2
+ * target_ruby.c
3
+ *
4
+ * Target language specific functions for cmpi_bindings
5
+ *
6
+ * Here: Ruby
7
+ *
8
+ * Written by Klaus Kaempf <kkaempf@suse.de>
9
+ *
10
+ */
11
+
12
+ /*****************************************************************************
13
+ * Copyright (C) 2008 Novell Inc. All rights reserved.
14
+ * Copyright (C) 2008 SUSE Linux Products GmbH. All rights reserved.
15
+ *
16
+ * Redistribution and use in source and binary forms, with or without
17
+ * modification, are permitted provided that the following conditions are met:
18
+ *
19
+ * - Redistributions of source code must retain the above copyright notice,
20
+ * this list of conditions and the following disclaimer.
21
+ *
22
+ * - Redistributions in binary form must reproduce the above copyright notice,
23
+ * this list of conditions and the following disclaimer in the documentation
24
+ * and/or other materials provided with the distribution.
25
+ *
26
+ * - Neither the name of Novell Inc. nor of SUSE Linux Products GmbH nor the
27
+ * names of its contributors may be used to endorse or promote products
28
+ * derived from this software without specific prior written permission.
29
+ *
30
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS IS''
31
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
32
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
33
+ * ARE DISCLAIMED. IN NO EVENT SHALL Novell Inc. OR SUSE Linux Products GmbH OR
34
+ * THE CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
35
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
36
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
37
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
38
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
39
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
40
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
41
+ *****************************************************************************/
42
+
43
+ /*
44
+ * How it works
45
+ *
46
+ * This target language adapter provides three functions for use by cmpi-provider.c
47
+ *
48
+ * static int TargetInitialize(ProviderMIHandle* hdl, CMPIStatus* st)
49
+ * static int TargetCall(ProviderMIHandle* hdl, CMPIStatus* st, const char* opname, int nargs, ...)
50
+ * static void TargetCleanup(ProviderMIHandle * hdl)
51
+ *
52
+ * TargetInitialize
53
+ * - loads the provider pointed to by ProviderMIHandle#miName
54
+ * miName - managed interface name - is FooBar (camel case) and loads foo_bar.rb
55
+ * expecting class Cmpi::FooBar
56
+ *
57
+ * TargetCall
58
+ * - calls opname with args of ProviderMIHandle#implementation
59
+ *
60
+ * TargetCleanup
61
+ * - tear down provider
62
+ */
63
+
64
+ #include <ctype.h>
65
+ #include <sys/types.h>
66
+ #include <sys/stat.h>
67
+ #include <unistd.h>
68
+
69
+ /* the module name for all Ruby code */
70
+ #define RB_MODULE_NAME "Cmpi"
71
+
72
+ /* an optional environment variable pointing to a directory with Ruby providers */
73
+ #define RUBY_PROVIDERS_DIR_ENV "RUBY_PROVIDERS_DIR"
74
+
75
+ /* mutex to flag Ruby call in progress - the one aquiring the lock inits the stack */
76
+ static pthread_mutex_t _stack_init_mutex = PTHREAD_MUTEX_INITIALIZER;
77
+
78
+ static void
79
+ decamelize(const char *from, char *to)
80
+ {
81
+ const char *start = from;
82
+ /* copy/decamelize classname */
83
+ while (*from) {
84
+ if (isupper(*from)) {
85
+ if (from > start /* not first char */
86
+ && (*(to-1) != '_')
87
+ && (islower(*(from-1)) || islower(*(from+1))) ) { /* last was lower or next is lower */
88
+ *to++ = '_';
89
+ }
90
+ *to++ = tolower(*from++);
91
+ }
92
+ else {
93
+ *to++ = *from++;
94
+ }
95
+ }
96
+ *to = 0;
97
+ }
98
+
99
+ /*
100
+ * load_module - load provider
101
+ *
102
+ * separate function for rb_require so it can be wrapped into rb_protect()
103
+ *
104
+ * Returns Cmpi::<Class>
105
+ *
106
+ */
107
+
108
+ static VALUE
109
+ load_provider(VALUE arg)
110
+ {
111
+ const char *classname = (const char *)arg;
112
+ VALUE req; /* result of rb_require */
113
+ if (classname == NULL || *classname == 0) {
114
+ _SBLIM_TRACE(1,("Ruby: load_provider(%s) no class given", classname));
115
+ return Qfalse;
116
+ }
117
+ char *filename = alloca(strlen(classname) * 2 + 1);
118
+ decamelize(classname, filename);
119
+ ruby_script(filename);
120
+ _SBLIM_TRACE(1,("Ruby: loading (%s)", filename));
121
+ req = rb_require(filename);
122
+ /* Qtrue == just loaded, Qfalse = already loaded, else: fail */
123
+ if ((req != Qtrue) && (req != Qfalse)) {
124
+ _SBLIM_TRACE(1,("<%d> require '%s' failed", getpid(), filename));
125
+ return Qnil;
126
+ }
127
+ /* Get Cmpi::Provider */
128
+ VALUE val = rb_const_get(rb_cObject, rb_intern(RB_MODULE_NAME));
129
+ if (val == Qnil) {
130
+ _SBLIM_TRACE(1,("<%d> No such module '%s'", getpid(), RB_MODULE_NAME));
131
+ return val;
132
+ }
133
+ val = rb_const_get(val, rb_intern(classname));
134
+ if (val == Qnil) {
135
+ _SBLIM_TRACE(1,("<%d> No such class '%s::%s'", getpid(), RB_MODULE_NAME, classname));
136
+ }
137
+ return val;
138
+ }
139
+
140
+
141
+ /*
142
+ * call_mi (called from rb_protect)
143
+ * call function of instance
144
+ *
145
+ * I args: pointer to array of at least 3 values
146
+ * args[0] -> (VALUE) instance
147
+ * args[1] -> (VALUE) id of function
148
+ * args[2] -> (int) number of arguments
149
+ * args[3...n] -> (VALUE) arguments
150
+ */
151
+
152
+ static VALUE
153
+ call_mi(VALUE args)
154
+ {
155
+ VALUE *values = (VALUE *)args;
156
+ return rb_funcall3(values[0], values[1], (int)values[2], values+3);
157
+ }
158
+
159
+
160
+
161
+ /*
162
+ * get Ruby exception trace -> CMPIString
163
+ *
164
+ */
165
+
166
+ #define TB_ERROR(str) {tbstr = str; goto cleanup;}
167
+ static CMPIString *
168
+ get_exc_trace(const CMPIBroker* broker)
169
+ {
170
+ VALUE exception = rb_gv_get("$!"); /* get last exception */
171
+ VALUE reason = rb_funcall(exception, rb_intern("to_s"), 0 );
172
+ VALUE trace = rb_gv_get("$@"); /* get last exception trace */
173
+ VALUE backtrace = rb_funcall(trace, rb_intern("join"), 1, rb_str_new("\n\t", 2));
174
+
175
+ char* tmp = fmtstr("%s\n\t%s", StringValuePtr(reason), StringValuePtr(backtrace));
176
+ return broker->eft->newString(broker, tmp, NULL);
177
+ }
178
+
179
+
180
+ /*
181
+ * Global Ruby initializer
182
+ *
183
+ * ** called with mutex locked **
184
+ *
185
+ * loads the Ruby interpreter
186
+ * init threads
187
+ */
188
+
189
+ static int
190
+ RbGlobalInitialize(const CMPIBroker* broker, CMPIStatus* st)
191
+ {
192
+ int error = 0;
193
+ char *loadpath;
194
+ VALUE searchpath;
195
+
196
+ if (_TARGET_INIT) {
197
+ return error;
198
+ }
199
+ _TARGET_INIT=1; /* safe, since mutex is locked */
200
+
201
+ _SBLIM_TRACE(1,("<%d> Ruby: Loading", getpid()));
202
+
203
+ ruby_init();
204
+ ruby_init_loadpath();
205
+ extern void SWIG_init();
206
+ SWIG_init();
207
+
208
+ searchpath = rb_gv_get("$:");
209
+ /* Append /usr/share/cmpi to $: */
210
+ rb_ary_push(searchpath, rb_str_new2("/usr/share/cmpi"));
211
+
212
+ /* Check RUBY_PROVIDERS_DIR_ENV if its a dir, append to $: */
213
+ loadpath = getenv(RUBY_PROVIDERS_DIR_ENV);
214
+ if (loadpath) {
215
+ struct stat buf;
216
+ if (stat(loadpath, &buf)) {
217
+ _SBLIM_TRACE(1,("<%d> Can't stat $RUBY_PROVIDERS_DIR '%s'", getpid(), loadpath));
218
+ return -1;
219
+ }
220
+ if ((buf.st_mode & S_IFDIR) == 0) {
221
+ _SBLIM_TRACE(1,("<%d> Not a directory: $RUBY_PROVIDERS_DIR '%s'", getpid(), loadpath));
222
+ return -1;
223
+ }
224
+ rb_ary_push(searchpath, rb_str_new2(loadpath));
225
+ }
226
+ return error;
227
+ }
228
+
229
+
230
+ /*---------------------------------------------------------------*/
231
+
232
+ /*
233
+ * local (per MI) Ruby initializer
234
+ * keeps track of reference count
235
+ *
236
+ */
237
+
238
+ static int
239
+ TargetInitialize(ProviderMIHandle* hdl, CMPIStatus* st)
240
+ {
241
+ VALUE args[6];
242
+ int error;
243
+ int have_lock = 0;
244
+
245
+ /* Set _CMPI_INIT, protected by _CMPI_INIT_MUTEX
246
+ * so we call ruby_finalize() only once.
247
+ */
248
+ if (pthread_mutex_lock(&_CMPI_INIT_MUTEX)) {
249
+ perror("Can't lock _CMPI_INIT_MUTEX");
250
+ abort();
251
+ }
252
+ error = RbGlobalInitialize(hdl->broker, st);
253
+ pthread_mutex_unlock(&_CMPI_INIT_MUTEX);
254
+ if (error != 0) {
255
+ if (st != NULL) {
256
+ st->rc = CMPI_RC_ERR_INVALID_CLASS;
257
+ st->msg = CMNewString(hdl->broker, "Failed to init Ruby", NULL);
258
+ }
259
+ goto fail;
260
+ }
261
+
262
+ _SBLIM_TRACE(1,("<%d> TargetInitialize(Ruby) called, miName '%s'", getpid(), hdl->miName));
263
+
264
+ if (pthread_mutex_trylock(&_stack_init_mutex) == 0) {
265
+ have_lock = 1;
266
+ RUBY_INIT_STACK
267
+ }
268
+
269
+ /* call static VALUE load_provider(const char *classname)
270
+ returns Cmpi::<Class>
271
+ */
272
+ args[0] = rb_protect(load_provider, (VALUE)hdl->miName, &error);
273
+ if (error) {
274
+ if (st != NULL) {
275
+ st->rc = CMPI_RC_ERR_INVALID_CLASS;
276
+ st->msg = CMNewString(hdl->broker, "Failed to load provider", NULL);
277
+ }
278
+ goto fail;
279
+ }
280
+
281
+ args[1] = rb_intern("new");
282
+ args[2] = 3;
283
+ args[3] = rb_str_new2(hdl->miName);
284
+ args[4] = SWIG_NewPointerObj((void*) hdl->broker, SWIGTYPE_p__CMPIBroker, 0);
285
+ args[5] = SWIG_NewPointerObj((void*) hdl->context, SWIGTYPE_p__CMPIContext, 0);
286
+ hdl->implementation = rb_protect(call_mi, (VALUE)args, &error);
287
+
288
+ fail:
289
+ if (error) {
290
+ CMPIString *trace = get_exc_trace(hdl->broker);
291
+ _SBLIM_TRACE(1,("Ruby: FAILED creating %s: %s", hdl->miName, CMGetCharPtr(trace)));
292
+ if (st != NULL) {
293
+ st->rc = CMPI_RC_ERR_INVALID_CLASS;
294
+ st->msg = trace;
295
+ }
296
+ }
297
+ else {
298
+ /* prevent Ruby GC from deallocating the provider
299
+ * found at http://www.lysator.liu.se/~norling/ruby_callbacks.html
300
+ */
301
+ rb_gc_register_address(&(hdl->implementation));
302
+ }
303
+ if (have_lock)
304
+ pthread_mutex_unlock(&_stack_init_mutex);
305
+ _SBLIM_TRACE(1,("Initialize() %s", (error == 0)?"succeeded":"failed"));
306
+ return error;
307
+ }
308
+
309
+
310
+ /*
311
+ * TargetCall
312
+ *
313
+ * Call function 'opname' with nargs arguments within managed interface hdl->implementation
314
+ */
315
+
316
+ static Target_Type
317
+ TargetCall(ProviderMIHandle* hdl, CMPIStatus* st,
318
+ const char* opname, int nargs, ...)
319
+ {
320
+ int have_lock = 0;
321
+ int invoke = (nargs < 0) ? 1 : 0; /* invokeMethod style call */
322
+ int i;
323
+ VALUE *args, result, op = rb_intern(opname);
324
+ va_list vargs;
325
+
326
+ if (pthread_mutex_trylock(&_stack_init_mutex) == 0) {
327
+ have_lock = 1;
328
+ RUBY_INIT_STACK
329
+ }
330
+ if (invoke) {
331
+ va_start(vargs, nargs);
332
+ args = va_arg(vargs, VALUE *);
333
+ va_end(vargs);
334
+ nargs = -nargs;
335
+ }
336
+ /* add hdl->instance, op and nargs to the args array, so rb_protect can be called */
337
+ nargs += 3;
338
+ if (!invoke) {
339
+ args = (VALUE *)alloca(nargs * sizeof(VALUE));
340
+ if (args == NULL) {
341
+ _SBLIM_TRACE(1,("Out of memory"));
342
+ abort();
343
+ }
344
+ }
345
+ args[0] = (VALUE)(hdl->implementation);
346
+ args[1] = op;
347
+ args[2] = (VALUE)(nargs-3);
348
+ if (!invoke && (nargs > 3))
349
+ {
350
+ va_start(vargs, nargs);
351
+ for (i = 3; i < nargs; ++i)
352
+ {
353
+ VALUE value;
354
+ value = va_arg(vargs, VALUE);
355
+ args[i] = (value == (VALUE)NULL) ? Qnil : value;
356
+ }
357
+ va_end(vargs);
358
+ }
359
+
360
+ /* call the Ruby function
361
+ * possible results:
362
+ * i nonzero: Exception raised
363
+ * result == nil: not (or badly) implemented
364
+ * result == true: success
365
+ * result == Array: pair of CMPIStatus rc(int) and msg(string)
366
+ */
367
+ result = rb_protect(call_mi, (VALUE)args, &i);
368
+
369
+ if (i) /* exception ? */
370
+ {
371
+ CMPIString *trace = get_exc_trace(hdl->broker);
372
+ char* str = fmtstr("Ruby: calling '%s' failed: %s", opname, CMGetCharPtr(trace));
373
+ _SBLIM_TRACE(1,("%s", str));
374
+ st->rc = CMPI_RC_ERR_FAILED;
375
+ st->msg = hdl->broker->eft->newString(hdl->broker, str, NULL);
376
+ goto done;
377
+ }
378
+ if (NIL_P(result)) /* not or wrongly implemented */
379
+ {
380
+ st->rc = CMPI_RC_ERR_NOT_SUPPORTED;
381
+ goto done;
382
+ }
383
+
384
+ if (invoke) {
385
+ st->rc = CMPI_RC_OK;
386
+ goto done;
387
+ }
388
+
389
+ if (result != Qtrue)
390
+ {
391
+ VALUE resulta = rb_check_array_type(result);
392
+ VALUE rc, msg;
393
+ if (NIL_P(resulta))
394
+ {
395
+ char* str = fmtstr("Ruby: calling '%s' returned unknown result", opname);
396
+ st->rc = CMPI_RC_ERR_FAILED;
397
+ st->msg = hdl->broker->eft->newString(hdl->broker, str, NULL);
398
+ goto done;
399
+ }
400
+
401
+ rc = rb_ary_entry(resulta, 0);
402
+ msg = rb_ary_entry(resulta, 1);
403
+ if (!FIXNUM_P(rc))
404
+ {
405
+ char* str = fmtstr("Ruby: calling '%s' returned non-numeric rc code", opname);
406
+ st->rc = CMPI_RC_ERR_FAILED;
407
+ st->msg = hdl->broker->eft->newString(hdl->broker, str, NULL);
408
+ goto done;
409
+ }
410
+ st->rc = FIX2LONG(rc);
411
+ st->msg = hdl->broker->eft->newString(hdl->broker, StringValuePtr(msg), NULL);
412
+ goto done;
413
+ }
414
+
415
+ /* all is fine */
416
+ st->rc = CMPI_RC_OK;
417
+ done:
418
+ if (have_lock)
419
+ pthread_mutex_unlock(&_stack_init_mutex);
420
+ return result;
421
+ }
422
+
423
+
424
+ /*
425
+ * TargetCleanup
426
+ */
427
+
428
+ static void
429
+ TargetCleanup(ProviderMIHandle * hdl)
430
+ {
431
+ _SBLIM_TRACE(1,("TargetCleanup(hdl %p)", hdl));
432
+ /* free() provider instance */
433
+ if (hdl && hdl->implementation) {
434
+ _SBLIM_TRACE(1,("unregister(%p)", hdl->implementation));
435
+ rb_gc_unregister_address(&(hdl->implementation));
436
+ }
437
+
438
+ /* Decrement _MI_COUNT, protected by _CMPI_INIT_MUTEX
439
+ * call ruby_finalize when _MI_COUNT drops to zero
440
+ */
441
+ if (pthread_mutex_lock(&_CMPI_INIT_MUTEX))
442
+ {
443
+ perror("Can't lock _CMPI_INIT_MUTEX");
444
+ abort();
445
+ }
446
+ if (--_MI_COUNT > 0)
447
+ {
448
+ pthread_mutex_unlock(&_CMPI_INIT_MUTEX);
449
+ _SBLIM_TRACE(1,("_MI_COUNT > 0: %d", _MI_COUNT));
450
+ return;
451
+ }
452
+
453
+ if (_TARGET_INIT) // if Ruby is initialized and _MI_COUNT == 0, call ruby_finalize
454
+ {
455
+ _SBLIM_TRACE(1,("Calling ruby_finalize()"));
456
+ ruby_finalize();
457
+ _TARGET_INIT=0; // false
458
+ }
459
+ pthread_mutex_unlock(&_CMPI_INIT_MUTEX);
460
+ return;
461
+ }
462
+