1/* -*- C -*-
2 * $Id: handle.c 35321 2012-04-13 23:45:37Z drbrain $
3 */
4
5#include <ruby.h>
6#include "dl.h"
7
8VALUE rb_cDLHandle;
9
10#ifdef _WIN32
11# ifndef _WIN32_WCE
12static void *
13w32_coredll(void)
14{
15    MEMORY_BASIC_INFORMATION m;
16    memset(&m, 0, sizeof(m));
17    if( !VirtualQuery(_errno, &m, sizeof(m)) ) return NULL;
18    return m.AllocationBase;
19}
20# endif
21
22static int
23w32_dlclose(void *ptr)
24{
25# ifndef _WIN32_WCE
26    if( ptr == w32_coredll() ) return 0;
27# endif
28    if( FreeLibrary((HMODULE)ptr) ) return 0;
29    return errno = rb_w32_map_errno(GetLastError());
30}
31#define dlclose(ptr) w32_dlclose(ptr)
32#endif
33
34static void
35dlhandle_free(void *ptr)
36{
37    struct dl_handle *dlhandle = ptr;
38    if( dlhandle->ptr && dlhandle->open && dlhandle->enable_close ){
39	dlclose(dlhandle->ptr);
40    }
41}
42
43static size_t
44dlhandle_memsize(const void *ptr)
45{
46    return ptr ? sizeof(struct dl_handle) : 0;
47}
48
49static const rb_data_type_t dlhandle_data_type = {
50    "dl/handle",
51    {0, dlhandle_free, dlhandle_memsize,},
52};
53
54/*
55 * call-seq: close
56 *
57 * Close this DL::Handle.  Calling close more than once will raise a
58 * DL::DLError exception.
59 */
60VALUE
61rb_dlhandle_close(VALUE self)
62{
63    struct dl_handle *dlhandle;
64
65    TypedData_Get_Struct(self, struct dl_handle, &dlhandle_data_type, dlhandle);
66    if(dlhandle->open) {
67	int ret = dlclose(dlhandle->ptr);
68	dlhandle->open = 0;
69
70	/* Check dlclose for successful return value */
71	if(ret) {
72#if defined(HAVE_DLERROR)
73	    rb_raise(rb_eDLError, "%s", dlerror());
74#else
75	    rb_raise(rb_eDLError, "could not close handle");
76#endif
77	}
78	return INT2NUM(ret);
79    }
80    rb_raise(rb_eDLError, "dlclose() called too many times");
81
82    UNREACHABLE;
83}
84
85VALUE
86rb_dlhandle_s_allocate(VALUE klass)
87{
88    VALUE obj;
89    struct dl_handle *dlhandle;
90
91    obj = TypedData_Make_Struct(rb_cDLHandle, struct dl_handle, &dlhandle_data_type, dlhandle);
92    dlhandle->ptr  = 0;
93    dlhandle->open = 0;
94    dlhandle->enable_close = 0;
95
96    return obj;
97}
98
99static VALUE
100predefined_dlhandle(void *handle)
101{
102    VALUE obj = rb_dlhandle_s_allocate(rb_cDLHandle);
103    struct dl_handle *dlhandle = DATA_PTR(obj);
104
105    dlhandle->ptr = handle;
106    dlhandle->open = 1;
107    OBJ_FREEZE(obj);
108    return obj;
109}
110
111/*
112 * call-seq:
113 *    initialize(lib = nil, flags = DL::RTLD_LAZY | DL::RTLD_GLOBAL)
114 *
115 * Create a new handler that opens library named +lib+ with +flags+.  If no
116 * library is specified, RTLD_DEFAULT is used.
117 */
118VALUE
119rb_dlhandle_initialize(int argc, VALUE argv[], VALUE self)
120{
121    void *ptr;
122    struct dl_handle *dlhandle;
123    VALUE lib, flag;
124    char  *clib;
125    int   cflag;
126    const char *err;
127
128    switch( rb_scan_args(argc, argv, "02", &lib, &flag) ){
129      case 0:
130	clib = NULL;
131	cflag = RTLD_LAZY | RTLD_GLOBAL;
132	break;
133      case 1:
134	clib = NIL_P(lib) ? NULL : StringValuePtr(lib);
135	cflag = RTLD_LAZY | RTLD_GLOBAL;
136	break;
137      case 2:
138	clib = NIL_P(lib) ? NULL : StringValuePtr(lib);
139	cflag = NUM2INT(flag);
140	break;
141      default:
142	rb_bug("rb_dlhandle_new");
143    }
144
145    rb_secure(2);
146
147#if defined(_WIN32)
148    if( !clib ){
149	HANDLE rb_libruby_handle(void);
150	ptr = rb_libruby_handle();
151    }
152    else if( STRCASECMP(clib, "libc") == 0
153# ifdef RUBY_COREDLL
154	     || STRCASECMP(clib, RUBY_COREDLL) == 0
155	     || STRCASECMP(clib, RUBY_COREDLL".dll") == 0
156# endif
157	){
158# ifdef _WIN32_WCE
159	ptr = dlopen("coredll.dll", cflag);
160# else
161	ptr = w32_coredll();
162# endif
163    }
164    else
165#endif
166	ptr = dlopen(clib, cflag);
167#if defined(HAVE_DLERROR)
168    if( !ptr && (err = dlerror()) ){
169	rb_raise(rb_eDLError, "%s", err);
170    }
171#else
172    if( !ptr ){
173	err = dlerror();
174	rb_raise(rb_eDLError, "%s", err);
175    }
176#endif
177    TypedData_Get_Struct(self, struct dl_handle, &dlhandle_data_type, dlhandle);
178    if( dlhandle->ptr && dlhandle->open && dlhandle->enable_close ){
179	dlclose(dlhandle->ptr);
180    }
181    dlhandle->ptr = ptr;
182    dlhandle->open = 1;
183    dlhandle->enable_close = 0;
184
185    if( rb_block_given_p() ){
186	rb_ensure(rb_yield, self, rb_dlhandle_close, self);
187    }
188
189    return Qnil;
190}
191
192/*
193 * call-seq: enable_close
194 *
195 * Enable a call to dlclose() when this DL::Handle is garbage collected.
196 */
197VALUE
198rb_dlhandle_enable_close(VALUE self)
199{
200    struct dl_handle *dlhandle;
201
202    TypedData_Get_Struct(self, struct dl_handle, &dlhandle_data_type, dlhandle);
203    dlhandle->enable_close = 1;
204    return Qnil;
205}
206
207/*
208 * call-seq: disable_close
209 *
210 * Disable a call to dlclose() when this DL::Handle is garbage collected.
211 */
212VALUE
213rb_dlhandle_disable_close(VALUE self)
214{
215    struct dl_handle *dlhandle;
216
217    TypedData_Get_Struct(self, struct dl_handle, &dlhandle_data_type, dlhandle);
218    dlhandle->enable_close = 0;
219    return Qnil;
220}
221
222/*
223 * call-seq: close_enabled?
224 *
225 * Returns +true+ if dlclose() will be called when this DL::Handle is
226 * garbage collected.
227 */
228static VALUE
229rb_dlhandle_close_enabled_p(VALUE self)
230{
231    struct dl_handle *dlhandle;
232
233    TypedData_Get_Struct(self, struct dl_handle, &dlhandle_data_type, dlhandle);
234
235    if(dlhandle->enable_close) return Qtrue;
236    return Qfalse;
237}
238
239/*
240 * call-seq: to_i
241 *
242 * Returns the memory address for this handle.
243 */
244VALUE
245rb_dlhandle_to_i(VALUE self)
246{
247    struct dl_handle *dlhandle;
248
249    TypedData_Get_Struct(self, struct dl_handle, &dlhandle_data_type, dlhandle);
250    return PTR2NUM(dlhandle);
251}
252
253static VALUE dlhandle_sym(void *handle, const char *symbol);
254
255/*
256 * Document-method: sym
257 * Document-method: []
258 *
259 * call-seq: sym(name)
260 *
261 * Get the address as an Integer for the function named +name+.
262 */
263VALUE
264rb_dlhandle_sym(VALUE self, VALUE sym)
265{
266    struct dl_handle *dlhandle;
267
268    TypedData_Get_Struct(self, struct dl_handle, &dlhandle_data_type, dlhandle);
269    if( ! dlhandle->open ){
270	rb_raise(rb_eDLError, "closed handle");
271    }
272
273    return dlhandle_sym(dlhandle->ptr, StringValueCStr(sym));
274}
275
276#ifndef RTLD_NEXT
277#define RTLD_NEXT NULL
278#endif
279#ifndef RTLD_DEFAULT
280#define RTLD_DEFAULT NULL
281#endif
282
283/*
284 * Document-method: sym
285 * Document-method: []
286 *
287 * call-seq: sym(name)
288 *
289 * Get the address as an Integer for the function named +name+.  The function
290 * is searched via dlsym on RTLD_NEXT.  See man(3) dlsym() for more info.
291 */
292VALUE
293rb_dlhandle_s_sym(VALUE self, VALUE sym)
294{
295    return dlhandle_sym(RTLD_NEXT, StringValueCStr(sym));
296}
297
298static VALUE
299dlhandle_sym(void *handle, const char *name)
300{
301#if defined(HAVE_DLERROR)
302    const char *err;
303# define CHECK_DLERROR if( err = dlerror() ){ func = 0; }
304#else
305# define CHECK_DLERROR
306#endif
307    void (*func)();
308
309    rb_secure(2);
310#ifdef HAVE_DLERROR
311    dlerror();
312#endif
313    func = (void (*)())(VALUE)dlsym(handle, name);
314    CHECK_DLERROR;
315#if defined(FUNC_STDCALL)
316    if( !func ){
317	int  i;
318	int  len = (int)strlen(name);
319	char *name_n;
320#if defined(__CYGWIN__) || defined(_WIN32) || defined(__MINGW32__)
321	{
322	    char *name_a = (char*)xmalloc(len+2);
323	    strcpy(name_a, name);
324	    name_n = name_a;
325	    name_a[len]   = 'A';
326	    name_a[len+1] = '\0';
327	    func = dlsym(handle, name_a);
328	    CHECK_DLERROR;
329	    if( func ) goto found;
330	    name_n = xrealloc(name_a, len+6);
331	}
332#else
333	name_n = (char*)xmalloc(len+6);
334#endif
335	memcpy(name_n, name, len);
336	name_n[len++] = '@';
337	for( i = 0; i < 256; i += 4 ){
338	    sprintf(name_n + len, "%d", i);
339	    func = dlsym(handle, name_n);
340	    CHECK_DLERROR;
341	    if( func ) break;
342	}
343	if( func ) goto found;
344	name_n[len-1] = 'A';
345	name_n[len++] = '@';
346	for( i = 0; i < 256; i += 4 ){
347	    sprintf(name_n + len, "%d", i);
348	    func = dlsym(handle, name_n);
349	    CHECK_DLERROR;
350	    if( func ) break;
351	}
352      found:
353	xfree(name_n);
354    }
355#endif
356    if( !func ){
357	rb_raise(rb_eDLError, "unknown symbol \"%s\"", name);
358    }
359
360    return PTR2NUM(func);
361}
362
363void
364Init_dlhandle(void)
365{
366    /*
367     * Document-class: DL::Handle
368     *
369     * The DL::Handle is the manner to access the dynamic library
370     *
371     * == Example
372     *
373     * === Setup
374     *
375     *   libc_so = "/lib64/libc.so.6"
376     *   => "/lib64/libc.so.6"
377     *   @handle = DL::Handle.new(libc_so)
378     *   => #<DL::Handle:0x00000000d69ef8>
379     *
380     * === Setup, with flags
381     *
382     *   libc_so = "/lib64/libc.so.6"
383     *   => "/lib64/libc.so.6"
384     *   @handle = DL::Handle.new(libc_so, DL::RTLD_LAZY | DL::RTLD_GLOBAL)
385     *   => #<DL::Handle:0x00000000d69ef8>
386     *
387     * === Addresses to symbols
388     *
389     *   strcpy_addr = @handle['strcpy']
390     *   => 140062278451968
391     *
392     * or
393     *
394     *   strcpy_addr = @handle.sym('strcpy')
395     *   => 140062278451968
396     *
397     */
398    rb_cDLHandle = rb_define_class_under(rb_mDL, "Handle", rb_cObject);
399    rb_define_alloc_func(rb_cDLHandle, rb_dlhandle_s_allocate);
400    rb_define_singleton_method(rb_cDLHandle, "sym", rb_dlhandle_s_sym, 1);
401    rb_define_singleton_method(rb_cDLHandle, "[]", rb_dlhandle_s_sym,  1);
402
403    /* Document-const: NEXT
404     *
405     * A predefined pseudo-handle of RTLD_NEXT
406     *
407     * Which will find the next occurrence of a function in the search order
408     * after the current library.
409     */
410    rb_define_const(rb_cDLHandle, "NEXT", predefined_dlhandle(RTLD_NEXT));
411
412    /* Document-const: DEFAULT
413     *
414     * A predefined pseudo-handle of RTLD_DEFAULT
415     *
416     * Which will find the first occurrence of the desired symbol using the
417     * default library search order
418     */
419    rb_define_const(rb_cDLHandle, "DEFAULT", predefined_dlhandle(RTLD_DEFAULT));
420    rb_define_method(rb_cDLHandle, "initialize", rb_dlhandle_initialize, -1);
421    rb_define_method(rb_cDLHandle, "to_i", rb_dlhandle_to_i, 0);
422    rb_define_method(rb_cDLHandle, "close", rb_dlhandle_close, 0);
423    rb_define_method(rb_cDLHandle, "sym",  rb_dlhandle_sym, 1);
424    rb_define_method(rb_cDLHandle, "[]",  rb_dlhandle_sym,  1);
425    rb_define_method(rb_cDLHandle, "disable_close", rb_dlhandle_disable_close, 0);
426    rb_define_method(rb_cDLHandle, "enable_close", rb_dlhandle_enable_close, 0);
427    rb_define_method(rb_cDLHandle, "close_enabled?", rb_dlhandle_close_enabled_p, 0);
428}
429
430/* mode: c; tab-with=8; sw=4; ts=8; noexpandtab: */
431