aboutsummaryrefslogtreecommitdiff
blob: 9d2209ee8437fa6972312c19994ecb5e7393abc7 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
""" Various rpython-level functions for dlopen
"""

from rpython.rtyper.tool import rffi_platform
from rpython.rtyper.lltypesystem import rffi, lltype
from rpython.rlib.objectmodel import we_are_translated, not_rpython
from rpython.rlib.rarithmetic import r_uint
from rpython.translator.tool.cbuild import ExternalCompilationInfo
from rpython.translator.platform import platform

import sys, os, string

# maaaybe isinstance here would be better. Think
_MSVC = platform.name == "msvc"
_MINGW = platform.name == "mingw32"
_WIN32 = _MSVC or _MINGW
_MAC_OS = platform.name == "darwin"
_FREEBSD = sys.platform.startswith("freebsd")
_NETBSD = sys.platform.startswith("netbsd")

if _WIN32:
    from rpython.rlib import rwin32
    includes = ['windows.h']
else:
    includes = ['dlfcn.h']

if _MAC_OS:
    pre_include_bits = ['#define MACOSX']
else:
    pre_include_bits = []

if _FREEBSD or _NETBSD or _WIN32:
    libraries = []
else:
    libraries = ['dl']

# this 'eci' is also used in pypy/module/sys/initpath.py
eci = ExternalCompilationInfo(
    pre_include_bits = pre_include_bits,
    includes = includes,
    libraries = libraries,
)

class CConfig:
    _compilation_info_ = eci

    RTLD_LOCAL = rffi_platform.DefinedConstantInteger('RTLD_LOCAL')
    RTLD_GLOBAL = rffi_platform.DefinedConstantInteger('RTLD_GLOBAL')
    RTLD_NOW = rffi_platform.DefinedConstantInteger('RTLD_NOW')
    RTLD_LAZY = rffi_platform.DefinedConstantInteger('RTLD_LAZY')
    RTLD_NODELETE = rffi_platform.DefinedConstantInteger('RTLD_NODELETE')
    RTLD_NOLOAD = rffi_platform.DefinedConstantInteger('RTLD_NOLOAD')
    RTLD_DEEPBIND = rffi_platform.DefinedConstantInteger('RTLD_DEEPBIND')

class cConfig:
    pass

for k, v in rffi_platform.configure(CConfig).items():
    setattr(cConfig, k, v)

def external(name, args, result, **kwds):
    return rffi.llexternal(name, args, result, compilation_info=eci, **kwds)

class DLOpenError(Exception):
    def __init__(self, msg):
        self.msg = msg
    def __str__(self):
        return repr(self.msg)


if not _WIN32:
    c_dlopen = external('dlopen', [rffi.CCHARP, rffi.INT], rffi.VOIDP)
    c_dlclose = external('dlclose', [rffi.VOIDP], rffi.INT, releasegil=False)
    c_dlerror = external('dlerror', [], rffi.CCHARP)
    c_dlsym = external('dlsym', [rffi.VOIDP, rffi.CCHARP], rffi.VOIDP)

    DLLHANDLE = rffi.VOIDP

    RTLD_LOCAL = cConfig.RTLD_LOCAL
    RTLD_GLOBAL = cConfig.RTLD_GLOBAL
    RTLD_NOW = cConfig.RTLD_NOW
    RTLD_LAZY = cConfig.RTLD_LAZY

    def dlerror():
        # XXX this would never work on top of ll2ctypes, because
        # ctypes are calling dlerror itself, unsure if I can do much in this
        # area (nor I would like to)
        if not we_are_translated():
            return "error info not available, not translated"
        res = c_dlerror()
        if not res:
            return ""
        return rffi.charp2str(res)

    @not_rpython
    def _dlerror_on_dlopen_untranslated(name):
        # aaargh
        import ctypes
        name = rffi.charp2str(name)
        try:
            ctypes.CDLL(name)
        except OSError as e:
            # common case: ctypes fails too, with the real dlerror()
            # message in str(e).  Return that error message.
            return str(e)
        else:
            # uncommon case: may happen if 'name' is a linker script
            # (which the C-level dlopen() can't handle) and we are
            # directly running on pypy (whose implementation of ctypes
            # or cffi will resolve linker scripts).  In that case, 
            # unsure what we can do.
            return ("opening %r with ctypes.CDLL() works, "
                    "but not with c_dlopen()??" % (name,))

    def _retry_as_ldscript(err, mode):
        """ ld scripts are fairly straightforward to parse (the library we want
        is in a form like 'GROUP ( <actual-filepath.so>'. A simple state machine
        can parse that out (avoids regexes)."""

        parts = err.split(":")
        if len(parts) != 2:
            return lltype.nullptr(rffi.VOIDP.TO)
        fullpath = parts[0]
        actual = ""
        last_five = "     "
        state = 0
        ldscript = os.open(fullpath, os.O_RDONLY, 0777)
        c = os.read(ldscript, 1)
        while c != "":
            if state == 0:
                last_five += c
                last_five = last_five[1:6]
                if last_five == "GROUP":
                    state = 1
            elif state == 1:
                if c == "(":
                    state = 2
            elif state == 2:
                if c not in string.whitespace:
                    actual += c
                    state = 3
            elif state == 3:
                if c in string.whitespace or c == ")":
                    break
                else:
                    actual += c
            c = os.read(ldscript, 1)
        os.close(ldscript)
        if actual != "":
            a = rffi.str2charp(actual)
            lib = c_dlopen(a, rffi.cast(rffi.INT, mode))
            rffi.free_charp(a)
            return lib
        else:
            return lltype.nullptr(rffi.VOIDP.TO)

    def _dlopen_default_mode():
        """ The default dlopen mode if it hasn't been changed by the user.
        """
        mode = RTLD_NOW
        if RTLD_LOCAL is not None:
            mode |= RTLD_LOCAL
        return mode

    def dlopen(name, mode=-1):
        """ Wrapper around C-level dlopen
        """
        if mode == -1:
            mode = _dlopen_default_mode()
        elif (mode & (RTLD_LAZY | RTLD_NOW)) == 0:
            mode |= RTLD_NOW
        #
        # haaaack for 'pypy py.test -A' if libm.so is a linker script
        # (see reason in _dlerror_on_dlopen_untranslated())
        must_free = False
        if not we_are_translated() and platform.name == "linux":
            if name and rffi.charp2str(name) == 'libm.so':
                name = rffi.str2charp('libm.so.6')
                must_free = True
        #
        res = c_dlopen(name, rffi.cast(rffi.INT, mode))
        if must_free:
            rffi.free_charp(name)
        if not res:
            if not we_are_translated():
                err = _dlerror_on_dlopen_untranslated(name)
            else:
                err = dlerror()
            if platform.name == "linux" and 'invalid ELF header' in err:
                # some linux distros put ld linker scripts in .so files
                # to load libraries more dynamically. The error contains the
                # full path to something that is probably a script to load
                # the library we want.
                res = _retry_as_ldscript(err, mode)
                if not res:
                    raise DLOpenError(err)
                return res
            else:
                raise DLOpenError(err)
        return res

    dlclose = c_dlclose

    def dlsym(libhandle, name):
        """ Wrapper around C-level dlsym
        """
        res = c_dlsym(libhandle, name)
        if not res:
            raise KeyError(name)
        # XXX rffi.cast here...
        return res

    def dlsym_byordinal(handle, index):
        # Never called
        raise KeyError(index)

else:  # _WIN32
    DLLHANDLE = rwin32.HMODULE
    RTLD_GLOBAL = None

    def _dlopen_default_mode():
        """ The default dlopen mode if it hasn't been changed by the user.
        """
        return 0

    def dlopen(name, mode=-1):
        # mode is unused on windows, but a consistant signature
        res = rwin32.LoadLibrary(name)
        if not res:
            err = rwin32.GetLastError_saved()
            ustr, lgt = rwin32.FormatErrorW(err)
            raise DLOpenError(ustr)
        return res

    def dlopenex(name):
        res = rwin32.LoadLibraryExA(name)
        if not res:
            err = rwin32.GetLastError_saved()
            ustr, lgt = rwin32.FormatErrorW(err)
            raise DLOpenError(ustr)
        return res

    def dlopenU(name, mode=-1):
        # mode is unused on windows, but a consistant signature
        res = rwin32.LoadLibraryW(name)
        if not res:
            err = rwin32.GetLastError_saved()
            ustr, lgt = rwin32.FormatErrorW(err)
            raise DLOpenError(ustr)
        return res

    def dlclose(handle):
        res = rwin32.FreeLibrary(handle)
        if res:
            return 0    # success
        else:
            return -1   # error

    def dlsym(handle, name):
        res = rwin32.GetProcAddress(handle, name)
        if not res:
            raise KeyError(name)
        # XXX rffi.cast here...
        return res

    def dlsym_byordinal(handle, index):
        # equivalent to MAKEINTRESOURCEA
        intresource = rffi.cast(rffi.CCHARP, r_uint(index) & 0xFFFF)
        res = rwin32.GetProcAddress(handle, intresource)
        if not res:
            raise KeyError(index)
        # XXX rffi.cast here...
        return res

    LoadLibrary = rwin32.LoadLibrary
    GetModuleHandle = rwin32.GetModuleHandle