|
| 1 | +#include <ruby.h> |
| 2 | +#include <dlfcn.h> |
| 3 | +#include <string.h> |
| 4 | +#include <stdint.h> |
| 5 | +#include <stdlib.h> |
| 6 | + |
| 7 | +// Load a Ruby extension like Ruby does, only with flags that: |
| 8 | +// a) hide symbols from other extensions (RTLD_LOCAL) |
| 9 | +// b) bind symbols tightly (RTLD_DEEPBIND, when available) |
| 10 | + |
| 11 | +void Init_mini_racer_loader(void); |
| 12 | + |
| 13 | +static void *_dln_load(const char *file); |
| 14 | + |
| 15 | +static VALUE _load_shared_lib(VALUE self, volatile VALUE fname) |
| 16 | +{ |
| 17 | + (void) self; |
| 18 | + |
| 19 | + // check that path is not tainted |
| 20 | + SafeStringValue(fname); |
| 21 | + |
| 22 | + FilePathValue(fname); |
| 23 | + VALUE path = rb_str_encode_ospath(fname); |
| 24 | + |
| 25 | + char *loc = StringValueCStr(path); |
| 26 | + void *handle = _dln_load(loc); |
| 27 | + |
| 28 | + return handle ? Qtrue : Qfalse; |
| 29 | +} |
| 30 | + |
| 31 | +// adapted from Ruby's dln.c |
| 32 | +#define INIT_FUNC_PREFIX ((char[]) {'I', 'n', 'i', 't', '_'}) |
| 33 | +#define INIT_FUNCNAME(buf, file) do { \ |
| 34 | + const char *base = (file); \ |
| 35 | + const size_t flen = _init_funcname(&base); \ |
| 36 | + const size_t plen = sizeof(INIT_FUNC_PREFIX); \ |
| 37 | + char *const tmp = ALLOCA_N(char, plen + flen + 1); \ |
| 38 | + memcpy(tmp, INIT_FUNC_PREFIX, plen); \ |
| 39 | + memcpy(tmp+plen, base, flen); \ |
| 40 | + tmp[plen+flen] = '\0'; \ |
| 41 | + *(buf) = tmp; \ |
| 42 | +} while(0) |
| 43 | + |
| 44 | +// adapted from Ruby's dln.c |
| 45 | +static size_t _init_funcname(const char **file) |
| 46 | +{ |
| 47 | + const char *p = *file, |
| 48 | + *base, |
| 49 | + *dot = NULL; |
| 50 | + |
| 51 | + for (base = p; *p; p++) { /* Find position of last '/' */ |
| 52 | + if (*p == '.' && !dot) { |
| 53 | + dot = p; |
| 54 | + } |
| 55 | + if (*p == '/') { |
| 56 | + base = p + 1; |
| 57 | + dot = NULL; |
| 58 | + } |
| 59 | + } |
| 60 | + *file = base; |
| 61 | + return (uintptr_t) ((dot ? dot : p) - base); |
| 62 | +} |
| 63 | + |
| 64 | +// adapted from Ruby's dln.c |
| 65 | +static void *_dln_load(const char *file) |
| 66 | +{ |
| 67 | + char *buf; |
| 68 | + const char *error; |
| 69 | +#define DLN_ERROR() (error = dlerror(), strcpy(ALLOCA_N(char, strlen(error) + 1), error)) |
| 70 | + |
| 71 | + void *handle; |
| 72 | + void (*init_fct)(void); |
| 73 | + |
| 74 | + INIT_FUNCNAME(&buf, file); |
| 75 | + |
| 76 | +#ifndef RTLD_DEEPBIND |
| 77 | +# define RTLD_DEEPBIND 0 |
| 78 | +#endif |
| 79 | + /* Load file */ |
| 80 | + if ((handle = dlopen(file, RTLD_NOW|RTLD_LOCAL|RTLD_DEEPBIND)) == NULL) { |
| 81 | + DLN_ERROR(); |
| 82 | + goto failed; |
| 83 | + } |
| 84 | +#if defined(RUBY_EXPORT) |
| 85 | + { |
| 86 | + static const char incompatible[] = "incompatible library version"; |
| 87 | + void *ex = dlsym(handle, "ruby_xmalloc"); |
| 88 | + if (ex && ex != (void *) &ruby_xmalloc) { |
| 89 | + |
| 90 | +# if defined __APPLE__ |
| 91 | + /* dlclose() segfaults */ |
| 92 | + rb_fatal("%s - %s", incompatible, file); |
| 93 | +# else |
| 94 | + dlclose(handle); |
| 95 | + error = incompatible; |
| 96 | + goto failed; |
| 97 | +#endif |
| 98 | + } |
| 99 | + } |
| 100 | +# endif |
| 101 | + |
| 102 | + init_fct = (void (*)(void)) dlsym(handle, buf); |
| 103 | + if (init_fct == NULL) { |
| 104 | + error = DLN_ERROR(); |
| 105 | + dlclose(handle); |
| 106 | + goto failed; |
| 107 | + } |
| 108 | + |
| 109 | + /* Call the init code */ |
| 110 | + (*init_fct)(); |
| 111 | + |
| 112 | + return handle; |
| 113 | + |
| 114 | +failed: |
| 115 | + rb_raise(rb_eLoadError, "%s", error); |
| 116 | +} |
| 117 | + |
| 118 | +__attribute__((visibility("default"))) void Init_mini_racer_loader() |
| 119 | +{ |
| 120 | + VALUE mSqreen = rb_define_module("MiniRacer"); |
| 121 | + VALUE mPrvExtLoader = rb_define_module_under(mSqreen, "Loader"); |
| 122 | + rb_define_singleton_method(mPrvExtLoader, "load", _load_shared_lib, 1); |
| 123 | +} |
0 commit comments