/* ffi.dlopen() interface with dlopen()/dlsym()/dlclose() */

static void *cdlopen_fetch(PyObject *libname, void *libhandle, char *symbol)
{
    void *address;

    if (libhandle == NULL) {
        PyErr_Format(FFIError, "library '%s' has been closed",
                     PyText_AS_UTF8(libname));
        return NULL;
    }

    dlerror();   /* clear error condition */
    address = dlsym(libhandle, symbol);
    if (address == NULL) {
        const char *error = dlerror();
        PyErr_Format(FFIError, "symbol '%s' not found in library '%s': %s",
                     symbol, PyText_AS_UTF8(libname), error);
    }
    return address;
}

static void cdlopen_close_ignore_errors(void *libhandle)
{
    if (libhandle != NULL)
        dlclose(libhandle);
}

static int cdlopen_close(PyObject *libname, void *libhandle)
{
    if (libhandle != NULL && dlclose(libhandle) != 0) {
        const char *error = dlerror();
        PyErr_Format(FFIError, "closing library '%s': %s",
                     PyText_AS_UTF8(libname), error);
        return -1;
    }
    return 0;
}

static PyObject *ffi_dlopen(PyObject *self, PyObject *args)
{
    char *filename_or_null, *printable_filename;
    void *handle;
    int flags = 0;

    if (PyTuple_GET_SIZE(args) == 0 || PyTuple_GET_ITEM(args, 0) == Py_None) {
        PyObject *dummy;
        if (!PyArg_ParseTuple(args, "|Oi:load_library",
                              &dummy, &flags))
            return NULL;
        filename_or_null = NULL;
    }
    else if (!PyArg_ParseTuple(args, "et|i:load_library",
                          Py_FileSystemDefaultEncoding, &filename_or_null,
                          &flags))
        return NULL;

    if ((flags & (RTLD_NOW | RTLD_LAZY)) == 0)
        flags |= RTLD_NOW;
    printable_filename = filename_or_null ? filename_or_null : "<None>";

    handle = dlopen(filename_or_null, flags);
    if (handle == NULL) {
        const char *error = dlerror();
        PyErr_Format(PyExc_OSError, "cannot load library '%s': %s",
                     printable_filename, error);
        return NULL;
    }
    return (PyObject *)lib_internal_new((FFIObject *)self,
                                        printable_filename, handle);
}

static PyObject *ffi_dlclose(PyObject *self, PyObject *args)
{
    LibObject *lib;
    void *libhandle;
    if (!PyArg_ParseTuple(args, "O!", &Lib_Type, &lib))
        return NULL;

    libhandle = lib->l_libhandle;
    lib->l_libhandle = NULL;

    if (libhandle == NULL) {
        PyErr_Format(FFIError, "library '%s' is already closed "
                     "or was not created with ffi.dlopen()",
                     PyText_AS_UTF8(lib->l_libname));
        return NULL;
    }

    /* Clear the dict to force further accesses to do cdlopen_fetch()
       again, and fail because the library was closed. */
    PyDict_Clear(lib->l_dict);

    if (cdlopen_close(lib->l_libname, libhandle) < 0)
        return NULL;

    Py_INCREF(Py_None);
    return Py_None;
}


static Py_ssize_t cdl_4bytes(char *src)
{
    /* read 4 bytes in little-endian order; return it as a signed integer */
    signed char *ssrc = (signed char *)src;
    unsigned char *usrc = (unsigned char *)src;
    return (ssrc[0] << 24) | (usrc[1] << 16) | (usrc[2] << 8) | usrc[3];
}

static _cffi_opcode_t cdl_opcode(char *src)
{
    return (_cffi_opcode_t)cdl_4bytes(src);
}

typedef struct {
    unsigned long long value;
    int neg;
} cdl_intconst_t;

static int _cdl_realize_global_int(struct _cffi_getconst_s *gc)
{
    /* The 'address' field of 'struct _cffi_global_s' is set to point
       to this function in case ffiobj_init() sees constant integers.
       This fishes around after the 'ctx->globals' array, which is
       initialized to contain another array, this time of
       'cdl_intconst_t' structures.  We get the nth one and it tells
       us what to return.
    */
    cdl_intconst_t *ic;
    ic = (cdl_intconst_t *)(gc->ctx->globals + gc->ctx->num_globals);
    ic += gc->gindex;
    gc->value = ic->value;
    return ic->neg;
}

static int ffiobj_init(PyObject *self, PyObject *args, PyObject *kwds)
{
    FFIObject *ffi;
    static char *keywords[] = {"module_name", "_version", "_types",
                               "_globals", "_struct_unions", "_enums",
                               "_typenames", "_includes", NULL};
    char *ffiname = "?", *types = NULL, *building = NULL;
    Py_ssize_t version = -1;
    Py_ssize_t types_len = 0;
    PyObject *globals = NULL, *struct_unions = NULL, *enums = NULL;
    PyObject *typenames = NULL, *includes = NULL;

    if (!PyArg_ParseTupleAndKeywords(args, kwds,
                                     "|sns#O!O!O!O!O!:FFI", keywords,
                                     &ffiname, &version, &types, &types_len,
                                     &PyTuple_Type, &globals,
                                     &PyTuple_Type, &struct_unions,
                                     &PyTuple_Type, &enums,
                                     &PyTuple_Type, &typenames,
                                     &PyTuple_Type, &includes))
        return -1;

    ffi = (FFIObject *)self;
    if (ffi->ctx_is_nonempty) {
        PyErr_SetString(PyExc_ValueError,
                        "cannot call FFI.__init__() more than once");
        return -1;
    }
    ffi->ctx_is_nonempty = 1;

    if (version == -1 && types_len == 0)
        return 0;
    if (version < CFFI_VERSION_MIN || version > CFFI_VERSION_MAX) {
        PyErr_Format(PyExc_ImportError,
                     "cffi out-of-line Python module '%s' has unknown "
                     "version %p", ffiname, (void *)version);
        return -1;
    }

    if (types_len > 0) {
        /* unpack a string of 4-byte entries into an array of _cffi_opcode_t */
        _cffi_opcode_t *ntypes;
        Py_ssize_t i, n = types_len / 4;

        building = PyMem_Malloc(n * sizeof(_cffi_opcode_t));
        if (building == NULL)
            goto error;
        ntypes = (_cffi_opcode_t *)building;

        for (i = 0; i < n; i++) {
            ntypes[i] = cdl_opcode(types);
            types += 4;
        }
        ffi->types_builder.ctx.types = ntypes;
        ffi->types_builder.ctx.num_types = n;
        building = NULL;
    }

    if (globals != NULL) {
        /* unpack a tuple alternating strings and ints, each two together
           describing one global_s entry with no specified address or size.
           The int is only used with integer constants. */
        struct _cffi_global_s *nglobs;
        cdl_intconst_t *nintconsts;
        Py_ssize_t i, n = PyTuple_GET_SIZE(globals) / 2;

        i = n * (sizeof(struct _cffi_global_s) + sizeof(cdl_intconst_t));
        building = PyMem_Malloc(i);
        if (building == NULL)
            goto error;
        memset(building, 0, i);
        nglobs = (struct _cffi_global_s *)building;
        nintconsts = (cdl_intconst_t *)(nglobs + n);

        for (i = 0; i < n; i++) {
            char *g = PyBytes_AS_STRING(PyTuple_GET_ITEM(globals, i * 2));
            nglobs[i].type_op = cdl_opcode(g); g += 4;
            nglobs[i].name = g;
            if (_CFFI_GETOP(nglobs[i].type_op) == _CFFI_OP_CONSTANT_INT ||
                _CFFI_GETOP(nglobs[i].type_op) == _CFFI_OP_ENUM) {
                PyObject *o = PyTuple_GET_ITEM(globals, i * 2 + 1);
                nglobs[i].address = &_cdl_realize_global_int;
#if PY_MAJOR_VERSION < 3
                if (PyInt_Check(o)) {
                    nintconsts[i].neg = PyInt_AS_LONG(o) <= 0;
                    nintconsts[i].value = (long long)PyInt_AS_LONG(o);
                }
                else
#endif
                {
                    nintconsts[i].neg = PyObject_RichCompareBool(o, Py_False,
                                                                 Py_LE);
                    nintconsts[i].value = PyLong_AsUnsignedLongLongMask(o);
                    if (PyErr_Occurred())
                        goto error;
                }
            }
        }
        ffi->types_builder.ctx.globals = nglobs;
        ffi->types_builder.ctx.num_globals = n;
        building = NULL;
    }

    if (struct_unions != NULL) {
        /* unpack a tuple of struct/unions, each described as a sub-tuple;
           the item 0 of each sub-tuple describes the struct/union, and
           the items 1..N-1 describe the fields, if any */
        struct _cffi_struct_union_s *nstructs;
        struct _cffi_field_s *nfields;
        Py_ssize_t i, n = PyTuple_GET_SIZE(struct_unions);
        Py_ssize_t nf = 0;   /* total number of fields */

        for (i = 0; i < n; i++) {
            nf += PyTuple_GET_SIZE(PyTuple_GET_ITEM(struct_unions, i)) - 1;
        }
        i = (n * sizeof(struct _cffi_struct_union_s) +
             nf * sizeof(struct _cffi_field_s));
        building = PyMem_Malloc(i);
        if (building == NULL)
            goto error;
        memset(building, 0, i);
        nstructs = (struct _cffi_struct_union_s *)building;
        nfields = (struct _cffi_field_s *)(nstructs + n);
        nf = 0;

        for (i = 0; i < n; i++) {
            /* 'desc' is the tuple of strings (desc_struct, desc_field_1, ..) */
            PyObject *desc = PyTuple_GET_ITEM(struct_unions, i);
            Py_ssize_t j, nf1 = PyTuple_GET_SIZE(desc) - 1;
            char *s = PyBytes_AS_STRING(PyTuple_GET_ITEM(desc, 0));
            /* 's' is the first string, describing the struct/union */
            nstructs[i].type_index = cdl_4bytes(s); s += 4;
            nstructs[i].flags = cdl_4bytes(s); s += 4;
            nstructs[i].name = s;
            if (nstructs[i].flags & (_CFFI_F_OPAQUE | _CFFI_F_EXTERNAL)) {
                nstructs[i].size = (size_t)-1;
                nstructs[i].alignment = -1;
                nstructs[i].first_field_index = -1;
                nstructs[i].num_fields = 0;
                assert(nf1 == 0);
            }
            else {
                nstructs[i].size = (size_t)-2;
                nstructs[i].alignment = -2;
                nstructs[i].first_field_index = nf;
                nstructs[i].num_fields = nf1;
            }
            for (j = 0; j < nf1; j++) {
                char *f = PyBytes_AS_STRING(PyTuple_GET_ITEM(desc, j + 1));
                /* 'f' is one of the other strings beyond the first one,
                   describing one field each */
                nfields[nf].field_type_op = cdl_opcode(f); f += 4;
                nfields[nf].field_offset = (size_t)-1;
                if (_CFFI_GETOP(nfields[nf].field_type_op) != _CFFI_OP_NOOP) {
                    nfields[nf].field_size = cdl_4bytes(f); f += 4;
                }
                else {
                    nfields[nf].field_size = (size_t)-1;
                }
                nfields[nf].name = f;
                nf++;
            }
        }
        ffi->types_builder.ctx.struct_unions = nstructs;
        ffi->types_builder.ctx.fields = nfields;
        ffi->types_builder.ctx.num_struct_unions = n;
        building = NULL;
    }

    if (enums != NULL) {
        /* unpack a tuple of strings, each of which describes one enum_s
           entry */
        struct _cffi_enum_s *nenums;
        Py_ssize_t i, n = PyTuple_GET_SIZE(enums);

        i = n * sizeof(struct _cffi_enum_s);
        building = PyMem_Malloc(i);
        if (building == NULL)
            goto error;
        memset(building, 0, i);
        nenums = (struct _cffi_enum_s *)building;

        for (i = 0; i < n; i++) {
            char *e = PyBytes_AS_STRING(PyTuple_GET_ITEM(enums, i));
            /* 'e' is a string describing the enum */
            nenums[i].type_index = cdl_4bytes(e); e += 4;
            nenums[i].type_prim = cdl_4bytes(e); e += 4;
            nenums[i].name = e; e += strlen(e) + 1;
            nenums[i].enumerators = e;
        }
        ffi->types_builder.ctx.enums = nenums;
        ffi->types_builder.ctx.num_enums = n;
        building = NULL;
    }

    if (typenames != NULL) {
        /* unpack a tuple of strings, each of which describes one typename_s
           entry */
        struct _cffi_typename_s *ntypenames;
        Py_ssize_t i, n = PyTuple_GET_SIZE(typenames);

        i = n * sizeof(struct _cffi_typename_s);
        building = PyMem_Malloc(i);
        if (building == NULL)
            goto error;
        memset(building, 0, i);
        ntypenames = (struct _cffi_typename_s *)building;

        for (i = 0; i < n; i++) {
            char *t = PyBytes_AS_STRING(PyTuple_GET_ITEM(typenames, i));
            /* 't' is a string describing the typename */
            ntypenames[i].type_index = cdl_4bytes(t); t += 4;
            ntypenames[i].name = t;
        }
        ffi->types_builder.ctx.typenames = ntypenames;
        ffi->types_builder.ctx.num_typenames = n;
        building = NULL;
    }

    if (includes != NULL) {
        PyObject *included_libs;

        included_libs = PyTuple_New(PyTuple_GET_SIZE(includes));
        if (included_libs == NULL)
            return -1;

        Py_INCREF(includes);
        ffi->types_builder.included_ffis = includes;
        ffi->types_builder.included_libs = included_libs;
    }

    /* Above, we took directly some "char *" strings out of the strings,
       typically from somewhere inside tuples.  Keep them alive by
       incref'ing the whole input arguments. */
    Py_INCREF(args);
    Py_XINCREF(kwds);
    ffi->types_builder._keepalive1 = args;
    ffi->types_builder._keepalive2 = kwds;
    return 0;

 error:
    if (building != NULL)
        PyMem_Free(building);
    if (!PyErr_Occurred())
        PyErr_NoMemory();
    return -1;
}
