diff --git a/extension/maxminddb.c b/extension/maxminddb.c index cd7a744..664ab8b 100644 --- a/extension/maxminddb.c +++ b/extension/maxminddb.c @@ -8,19 +8,39 @@ #define __STDC_FORMAT_MACROS #include <inttypes.h> -static PyTypeObject Reader_Type; -static PyTypeObject Metadata_Type; -static PyObject *MaxMindDB_error; +#ifdef __GNUC__ +#define UNUSED(x) UNUSED_##x __attribute__((__unused__)) +#else +#define UNUSED(x) UNUSED_##x +#endif + +typedef struct { + PyObject *ReaderType; + PyObject *MetadataType; + PyObject *MaxMindDB_error; +} _maxminddb_state; + +static inline _maxminddb_state* +get_maxminddb_state(PyObject *module) +{ + _maxminddb_state *state = PyModule_GetState(module); + assert(state != NULL); + return (_maxminddb_state *)state; +} + +static struct PyModuleDef maxminddb_module; + +#define _maxminddbstate_type(_t) \ + (get_maxminddb_state(PyType_GetModuleByDef(_t, &maxminddb_module))) -// clang-format off typedef struct { - PyObject_HEAD /* no semicolon */ + PyObject_HEAD MMDB_s *mmdb; PyObject *closed; -} Reader_obj; +} ReaderObject; typedef struct { - PyObject_HEAD /* no semicolon */ + PyObject_HEAD PyObject *binary_format_major_version; PyObject *binary_format_minor_version; PyObject *build_epoch; @@ -30,32 +50,28 @@ typedef struct { PyObject *languages; PyObject *node_count; PyObject *record_size; -} Metadata_obj; -// clang-format on +} MetadataObject; -static int get_record(PyObject *self, PyObject *args, PyObject **record); +static int get_record(ReaderObject *self, PyObject *args, PyObject **record); static bool format_sockaddr(struct sockaddr *addr, char *dst); -static PyObject *from_entry_data_list(MMDB_entry_data_list_s **entry_data_list); -static PyObject *from_map(MMDB_entry_data_list_s **entry_data_list); -static PyObject *from_array(MMDB_entry_data_list_s **entry_data_list); -static PyObject *from_uint128(const MMDB_entry_data_list_s *entry_data_list); +static PyObject *from_entry_data_list(ReaderObject *self, MMDB_entry_data_list_s **entry_data_list); +static PyObject *from_map(ReaderObject *self, MMDB_entry_data_list_s **entry_data_list); +static PyObject *from_array(ReaderObject *self, MMDB_entry_data_list_s **entry_data_list); +static PyObject *from_uint128(ReaderObject *self, const MMDB_entry_data_list_s *entry_data_list); static int ip_converter(PyObject *obj, struct sockaddr_storage *ip_address); -#ifdef __GNUC__ -#define UNUSED(x) UNUSED_##x __attribute__((__unused__)) -#else -#define UNUSED(x) UNUSED_##x -#endif - -static int Reader_init(PyObject *self, PyObject *args, PyObject *kwds) { +static int +ReaderType_init(ReaderObject *self, PyObject *args, PyObject *kwargs) +{ + static char *arg_names[] = {"database", "mode", NULL}; PyObject *filepath = NULL; int mode = 0; - static char *kwlist[] = {"database", "mode", NULL}; - if (!PyArg_ParseTupleAndKeywords(args, - kwds, - "O&|i", - kwlist, + _maxminddb_state *state = _maxminddbstate_type(Py_TYPE(self)); + assert(state != NULL); + + if (!PyArg_ParseTupleAndKeywords(args, kwargs, + "O&|i", arg_names, PyUnicode_FSConverter, &filepath, &mode)) { @@ -67,7 +83,7 @@ static int Reader_init(PyObject *self, PyObject *args, PyObject *kwds) { return -1; } - if (mode != 0 && mode != 1) { + if ((mode != 0) && (mode != 1)) { Py_XDECREF(filepath); PyErr_Format( PyExc_ValueError, @@ -77,34 +93,33 @@ static int Reader_init(PyObject *self, PyObject *args, PyObject *kwds) { return -1; } - if (0 != access(filename, R_OK)) { - + if (access(filename, R_OK) < 0) { PyErr_SetFromErrnoWithFilenameObject(PyExc_OSError, filepath); Py_XDECREF(filepath); return -1; } MMDB_s *mmdb = (MMDB_s *)malloc(sizeof(MMDB_s)); - if (NULL == mmdb) { + if (mmdb == NULL) { Py_XDECREF(filepath); PyErr_NoMemory(); return -1; } - Reader_obj *mmdb_obj = (Reader_obj *)self; - if (!mmdb_obj) { + ReaderObject *mmdb_obj = (ReaderObject *)self; + if (mmdb_obj == NULL) { Py_XDECREF(filepath); free(mmdb); PyErr_NoMemory(); return -1; } - int const status = MMDB_open(filename, MMDB_MODE_MMAP, mmdb); + uint16_t status = MMDB_open(filename, MMDB_MODE_MMAP, mmdb); Py_XDECREF(filepath); - if (MMDB_SUCCESS != status) { + if (status != MMDB_SUCCESS) { free(mmdb); - PyErr_Format(MaxMindDB_error, + PyErr_Format(state->MaxMindDB_error, "Error opening database file (%s). Is this a valid " "MaxMind DB file?", filename); @@ -113,21 +128,26 @@ static int Reader_init(PyObject *self, PyObject *args, PyObject *kwds) { mmdb_obj->mmdb = mmdb; mmdb_obj->closed = Py_False; + return 0; } -static PyObject *Reader_get(PyObject *self, PyObject *args) { +static PyObject * +Reader_get(ReaderObject *self, PyObject *args, PyObject *UNUSED(kwargs)) +{ PyObject *record = NULL; - if (get_record(self, args, &record) == -1) { + if (get_record(self, args, &record) < 0) { return NULL; } return record; } -static PyObject *Reader_get_with_prefix_len(PyObject *self, PyObject *args) { +static PyObject * +Reader_get_with_prefix_len(ReaderObject *self, PyObject *args, PyObject *UNUSED(kwargs)) +{ PyObject *record = NULL; int prefix_len = get_record(self, args, &record); - if (prefix_len == -1) { + if (prefix_len < 0) { return NULL; } @@ -137,9 +157,13 @@ static PyObject *Reader_get_with_prefix_len(PyObject *self, PyObject *args) { return tuple; } -static int get_record(PyObject *self, PyObject *args, PyObject **record) { - MMDB_s *mmdb = ((Reader_obj *)self)->mmdb; - if (NULL == mmdb) { +static int +get_record(ReaderObject *self, PyObject *args, PyObject **record) +{ + _maxminddb_state *state = _maxminddbstate_type(Py_TYPE(self)); + MMDB_s *mmdb = ((ReaderObject *)self)->mmdb; + + if (mmdb) { PyErr_SetString(PyExc_ValueError, "Attempt to read from a closed MaxMind DB."); return -1; @@ -165,7 +189,7 @@ static int get_record(PyObject *self, PyObject *args, PyObject **record) { if (MMDB_IPV6_LOOKUP_IN_IPV4_DATABASE_ERROR == mmdb_error) { exception = PyExc_ValueError; } else { - exception = MaxMindDB_error; + exception = state->MaxMindDB_error; } char ipstr[INET6_ADDRSTRLEN] = {0}; if (format_sockaddr(ip_address, ipstr)) { @@ -195,7 +219,7 @@ static int get_record(PyObject *self, PyObject *args, PyObject **record) { if (MMDB_SUCCESS != status) { char ipstr[INET6_ADDRSTRLEN] = {0}; if (format_sockaddr(ip_address, ipstr)) { - PyErr_Format(MaxMindDB_error, + PyErr_Format(state->MaxMindDB_error, "Error while looking up data for %s. %s", ipstr, MMDB_strerror(status)); @@ -205,7 +229,7 @@ static int get_record(PyObject *self, PyObject *args, PyObject **record) { } MMDB_entry_data_list_s *original_entry_data_list = entry_data_list; - *record = from_entry_data_list(&entry_data_list); + *record = from_entry_data_list(self, &entry_data_list); MMDB_free_entry_data_list(original_entry_data_list); // from_entry_data_list will return NULL on errors. @@ -216,7 +240,9 @@ static int get_record(PyObject *self, PyObject *args, PyObject **record) { return prefix_len; } -static int ip_converter(PyObject *obj, struct sockaddr_storage *ip_address) { +static int +ip_converter(PyObject *obj, struct sockaddr_storage *ip_address) +{ if (PyUnicode_Check(obj)) { Py_ssize_t len; const char *ipstr = PyUnicode_AsUTF8AndSize(obj, &len); @@ -276,14 +302,14 @@ static int ip_converter(PyObject *obj, struct sockaddr_storage *ip_address) { case 16: { ip_address->ss_family = AF_INET6; struct sockaddr_in6 *sin = (struct sockaddr_in6 *)ip_address; - memcpy(sin->sin6_addr.s6_addr, bytes, (size_t)len); + memcpy(sin->sin6_addr.s6_addr, bytes, len); Py_DECREF(packed); return 1; } case 4: { ip_address->ss_family = AF_INET; struct sockaddr_in *sin = (struct sockaddr_in *)ip_address; - memcpy(&(sin->sin_addr.s_addr), bytes, (size_t)len); + memcpy(&(sin->sin_addr.s_addr), bytes, len); Py_DECREF(packed); return 1; } @@ -296,7 +322,9 @@ static int ip_converter(PyObject *obj, struct sockaddr_storage *ip_address) { } } -static bool format_sockaddr(struct sockaddr *sa, char *dst) { +static bool +format_sockaddr(struct sockaddr *sa, char *dst) +{ char *addr; if (sa->sa_family == AF_INET) { struct sockaddr_in *sin = (struct sockaddr_in *)sa; @@ -313,10 +341,13 @@ static bool format_sockaddr(struct sockaddr *sa, char *dst) { return false; } -static PyObject *Reader_metadata(PyObject *self, PyObject *UNUSED(args)) { - Reader_obj *mmdb_obj = (Reader_obj *)self; +static PyObject * +Reader_metadata(ReaderObject *self, PyObject *UNUSED(args)) +{ + _maxminddb_state *state = PyType_GetModuleState(Py_TYPE(self)); + ReaderObject *mmdb_obj = (ReaderObject *)self; - if (NULL == mmdb_obj->mmdb) { + if (mmdb_obj->mmdb == NULL) { PyErr_SetString(PyExc_IOError, "Attempt to read from a closed MaxMind DB."); return NULL; @@ -326,44 +357,43 @@ static PyObject *Reader_metadata(PyObject *self, PyObject *UNUSED(args)) { MMDB_get_metadata_as_entry_data_list(mmdb_obj->mmdb, &entry_data_list); MMDB_entry_data_list_s *original_entry_data_list = entry_data_list; - PyObject *metadata_dict = from_entry_data_list(&entry_data_list); + PyObject *metadata_dict = from_entry_data_list(self, &entry_data_list); MMDB_free_entry_data_list(original_entry_data_list); - if (NULL == metadata_dict || !PyDict_Check(metadata_dict)) { - PyErr_SetString(MaxMindDB_error, "Error decoding metadata."); + if ((metadata_dict == NULL) || !PyDict_Check(metadata_dict)) { + PyErr_SetString(state->MaxMindDB_error, "Error decoding metadata."); return NULL; } PyObject *args = PyTuple_New(0); - if (NULL == args) { + if (args == NULL) { Py_DECREF(metadata_dict); return NULL; } - PyObject *metadata = - PyObject_Call((PyObject *)&Metadata_Type, args, metadata_dict); - + PyObject *metadata = PyObject_Call((PyObject *)state->MetadataType, args, metadata_dict); Py_DECREF(metadata_dict); + return metadata; } -static PyObject *Reader_close(PyObject *self, PyObject *UNUSED(args)) { - Reader_obj *mmdb_obj = (Reader_obj *)self; - - if (NULL != mmdb_obj->mmdb) { - MMDB_close(mmdb_obj->mmdb); - free(mmdb_obj->mmdb); - mmdb_obj->mmdb = NULL; +static PyObject * +Reader_close(ReaderObject *self, PyObject *UNUSED(args)) +{ + if (self->mmdb) { + MMDB_close(self->mmdb); + free(self->mmdb); + self->mmdb = NULL; } - mmdb_obj->closed = Py_True; + self->closed = Py_True; Py_RETURN_NONE; } -static PyObject *Reader__enter__(PyObject *self, PyObject *UNUSED(args)) { - Reader_obj *mmdb_obj = (Reader_obj *)self; - - if (mmdb_obj->closed == Py_True) { +static PyObject * +Reader__enter__(ReaderObject *self, PyObject *UNUSED(args)) +{ + if (self->closed == Py_True) { PyErr_SetString(PyExc_ValueError, "Attempt to reopen a closed MaxMind DB."); return NULL; @@ -373,96 +403,90 @@ static PyObject *Reader__enter__(PyObject *self, PyObject *UNUSED(args)) { return (PyObject *)self; } -static PyObject *Reader__exit__(PyObject *self, PyObject *UNUSED(args)) { +static PyObject * +Reader__exit__(ReaderObject *self, PyObject *UNUSED(args)) +{ Reader_close(self, NULL); Py_RETURN_NONE; } -static void Reader_dealloc(PyObject *self) { - Reader_obj *obj = (Reader_obj *)self; - if (NULL != obj->mmdb) { +static void +ReaderType_dealloc(ReaderObject *self) +{ + PyObject_GC_UnTrack(self); + if (self->mmdb) { Reader_close(self, NULL); } - PyObject_Del(self); } -static int Metadata_init(PyObject *self, PyObject *args, PyObject *kwds) { - - PyObject *binary_format_major_version, *binary_format_minor_version, - *build_epoch, *database_type, *description, *ip_version, *languages, - *node_count, *record_size; - - static char *kwlist[] = {"binary_format_major_version", - "binary_format_minor_version", - "build_epoch", - "database_type", - "description", - "ip_version", - "languages", - "node_count", - "record_size", - NULL}; - - if (!PyArg_ParseTupleAndKeywords(args, - kwds, +static int +MetadataType_init(MetadataObject *self, PyObject *args, PyObject *kwds) +{ + static char *arg_names[] = { + "binary_format_major_version", + "binary_format_minor_version", + "build_epoch", + "database_type", + "description", + "ip_version", + "languages", + "node_count", + "record_size", + NULL + }; + + if (!PyArg_ParseTupleAndKeywords(args, kwds, "|OOOOOOOOO", - kwlist, - &binary_format_major_version, - &binary_format_minor_version, - &build_epoch, - &database_type, - &description, - &ip_version, - &languages, - &node_count, - &record_size)) { + arg_names, + self->binary_format_major_version, + self->binary_format_minor_version, + self->build_epoch, + self->database_type, + self->description, + self->ip_version, + self->languages, + self->node_count, + self->record_size)) { return -1; } - Metadata_obj *obj = (Metadata_obj *)self; - - obj->binary_format_major_version = binary_format_major_version; - obj->binary_format_minor_version = binary_format_minor_version; - obj->build_epoch = build_epoch; - obj->database_type = database_type; - obj->description = description; - obj->ip_version = ip_version; - obj->languages = languages; - obj->node_count = node_count; - obj->record_size = record_size; - - Py_INCREF(obj->binary_format_major_version); - Py_INCREF(obj->binary_format_minor_version); - Py_INCREF(obj->build_epoch); - Py_INCREF(obj->database_type); - Py_INCREF(obj->description); - Py_INCREF(obj->ip_version); - Py_INCREF(obj->languages); - Py_INCREF(obj->node_count); - Py_INCREF(obj->record_size); + Py_INCREF(self->binary_format_major_version); + Py_INCREF(self->binary_format_minor_version); + Py_INCREF(self->build_epoch); + Py_INCREF(self->database_type); + Py_INCREF(self->description); + Py_INCREF(self->ip_version); + Py_INCREF(self->languages); + Py_INCREF(self->node_count); + Py_INCREF(self->record_size); return 0; } -static void Metadata_dealloc(PyObject *self) { - Metadata_obj *obj = (Metadata_obj *)self; - Py_DECREF(obj->binary_format_major_version); - Py_DECREF(obj->binary_format_minor_version); - Py_DECREF(obj->build_epoch); - Py_DECREF(obj->database_type); - Py_DECREF(obj->description); - Py_DECREF(obj->ip_version); - Py_DECREF(obj->languages); - Py_DECREF(obj->node_count); - Py_DECREF(obj->record_size); +static void +MetadataType_dealloc(MetadataObject *self) +{ + PyObject_GC_UnTracak(self); + Py_CLEAR(self->binary_format_major_version); + Py_CLEAR(self->binary_format_minor_version); + Py_CLEAR(self->build_epoch); + Py_CLEAR(self->database_type); + Py_CLEAR(self->description); + Py_CLEAR(self->ip_version); + Py_CLEAR(self->languages); + Py_CLEAR(self->node_count); + Py_CLEAR(self->record_size); PyObject_Del(self); } static PyObject * -from_entry_data_list(MMDB_entry_data_list_s **entry_data_list) { - if (NULL == entry_data_list || NULL == *entry_data_list) { - PyErr_SetString(MaxMindDB_error, +from_entry_data_list(ReaderObject *self, MMDB_entry_data_list_s **entry_data_list) +{ + _maxminddb_state *state = _maxminddbstate_type(Py_TYPE(self)); + + if ((entry_data_list == NULL) || (*entry_data_list == NULL)) { + PyErr_SetString(state->MaxMindDB_error, "Error while looking up data. Your database may be " "corrupt or you have found a bug in libmaxminddb."); return NULL; @@ -470,9 +494,9 @@ from_entry_data_list(MMDB_entry_data_list_s **entry_data_list) { switch ((*entry_data_list)->entry_data.type) { case MMDB_DATA_TYPE_MAP: - return from_map(entry_data_list); + return from_map(self, entry_data_list); case MMDB_DATA_TYPE_ARRAY: - return from_array(entry_data_list); + return from_array(self, entry_data_list); case MMDB_DATA_TYPE_UTF8_STRING: return PyUnicode_FromStringAndSize( (*entry_data_list)->entry_data.utf8_string, @@ -497,21 +521,22 @@ from_entry_data_list(MMDB_entry_data_list_s **entry_data_list) { return PyLong_FromUnsignedLongLong( (*entry_data_list)->entry_data.uint64); case MMDB_DATA_TYPE_UINT128: - return from_uint128(*entry_data_list); + return from_uint128(self, *entry_data_list); case MMDB_DATA_TYPE_INT32: return PyLong_FromLong((*entry_data_list)->entry_data.int32); default: - PyErr_Format(MaxMindDB_error, + PyErr_Format(state->MaxMindDB_error, "Invalid data type arguments: %d", (*entry_data_list)->entry_data.type); - return NULL; } return NULL; } -static PyObject *from_map(MMDB_entry_data_list_s **entry_data_list) { +static PyObject * +from_map(ReaderObject *self, MMDB_entry_data_list_s **entry_data_list) +{ PyObject *py_obj = PyDict_New(); - if (NULL == py_obj) { + if (py_obj == NULL) { PyErr_NoMemory(); return NULL; } @@ -526,7 +551,7 @@ static PyObject *from_map(MMDB_entry_data_list_s **entry_data_list) { *entry_data_list = (*entry_data_list)->next; PyObject *key = PyUnicode_FromStringAndSize( - (*entry_data_list)->entry_data.utf8_string, + (char *)(*entry_data_list)->entry_data.utf8_string, (*entry_data_list)->entry_data.data_size); if (!key) { // PyUnicode_FromStringAndSize will set an appropriate exception @@ -536,7 +561,7 @@ static PyObject *from_map(MMDB_entry_data_list_s **entry_data_list) { *entry_data_list = (*entry_data_list)->next; - PyObject *value = from_entry_data_list(entry_data_list); + PyObject *value = from_entry_data_list(self, entry_data_list); if (NULL == value) { Py_DECREF(key); Py_DECREF(py_obj); @@ -550,7 +575,9 @@ static PyObject *from_map(MMDB_entry_data_list_s **entry_data_list) { return py_obj; } -static PyObject *from_array(MMDB_entry_data_list_s **entry_data_list) { +static PyObject * +from_array(ReaderObject *self, MMDB_entry_data_list_s **entry_data_list) +{ const uint32_t size = (*entry_data_list)->entry_data.data_size; PyObject *py_obj = PyList_New(size); @@ -565,7 +592,7 @@ static PyObject *from_array(MMDB_entry_data_list_s **entry_data_list) { // coverity[check_after_deref] for (i = 0; i < size && entry_data_list; i++) { *entry_data_list = (*entry_data_list)->next; - PyObject *value = from_entry_data_list(entry_data_list); + PyObject *value = from_entry_data_list(self, entry_data_list); if (NULL == value) { Py_DECREF(py_obj); return NULL; @@ -573,10 +600,13 @@ static PyObject *from_array(MMDB_entry_data_list_s **entry_data_list) { // PyList_SetItem 'steals' the reference PyList_SetItem(py_obj, i, value); } + return py_obj; } -static PyObject *from_uint128(const MMDB_entry_data_list_s *entry_data_list) { +static PyObject * +from_uint128(ReaderObject *UNUSED(self), const MMDB_entry_data_list_s *entry_data_list) +{ uint64_t high = 0; uint64_t low = 0; #if MMDB_UINT128_IS_BYTE_ARRAY @@ -604,157 +634,185 @@ static PyObject *from_uint128(const MMDB_entry_data_list_s *entry_data_list) { PyObject *py_obj = PyLong_FromString(num_str, NULL, 16); free(num_str); + return py_obj; } -static PyMethodDef Reader_methods[] = { - {"get", - Reader_get, - METH_VARARGS, - "Return the record for the ip_address in the MaxMind DB"}, - {"get_with_prefix_len", - Reader_get_with_prefix_len, - METH_VARARGS, - "Return a tuple with the record and the associated prefix length"}, - {"metadata", - Reader_metadata, - METH_NOARGS, - "Return metadata object for database"}, - {"close", Reader_close, METH_NOARGS, "Closes database"}, - {"__exit__", - Reader__exit__, - METH_VARARGS, - "Called when exiting a with-context. Calls close"}, - {"__enter__", - Reader__enter__, - METH_NOARGS, - "Called when entering a with-context."}, - {NULL, NULL, 0, NULL}}; - -static PyMemberDef Reader_members[] = { - {"closed", T_OBJECT, offsetof(Reader_obj, closed), READONLY, NULL}, - {NULL, 0, 0, 0, NULL}}; - -// clang-format off -static PyTypeObject Reader_Type = { - PyVarObject_HEAD_INIT(NULL, 0) - .tp_basicsize = sizeof(Reader_obj), - .tp_dealloc = Reader_dealloc, - .tp_doc = "Reader object", - .tp_flags = Py_TPFLAGS_DEFAULT, - .tp_methods = Reader_methods, - .tp_members = Reader_members, - .tp_name = "Reader", - .tp_init = Reader_init, +/* + * Module + */ + +PyDoc_STRVAR(maxminddb_module_doc, +"MaxMind Database Reader\n" +"\n" +"This module is used for reading MaxMind DB files." +); + + +PyDoc_STRVAR(ReaderType_get_doc, "Return the record for the ip_address in the MaxMind DB"); +PyDoc_STRVAR(ReaderType_get_with_prefix_len_doc, "Return a tuple with the record and the associated prefix length"); +PyDoc_STRVAR(ReaderType_metadata_doc, "Return the metadata object for database"); +PyDoc_STRVAR(ReaderType_close_doc, "Closes database"); +PyDoc_STRVAR(ReaderType___exit___doc, "Called when exiting a with-context (calls close)"); +PyDoc_STRVAR(ReaderType___enter___doc, "Called when entering a with-context"); + +static PyMethodDef ReaderType_methods[] = { + { "get", (PyCFunction)Reader_get, METH_VARARGS, ReaderType_get_doc }, + { "get_with_prefix_len", (PyCFunction)Reader_get_with_prefix_len, METH_VARARGS, ReaderType_get_with_prefix_len_doc }, + { "metadata", (PyCFunction)Reader_metadata, METH_NOARGS, ReaderType_metadata_doc }, + { "close", (PyCFunction)Reader_close, METH_NOARGS, ReaderType_close_doc }, + { "__exit__", (PyCFunction)Reader__exit__, METH_VARARGS, ReaderType___exit___doc }, + { "__enter__", (PyCFunction)Reader__enter__, METH_NOARGS, ReaderType___enter___doc }, + { NULL, NULL } }; -// clang-format on - -static PyMethodDef Metadata_methods[] = {{NULL, NULL, 0, NULL}}; - -static PyMemberDef Metadata_members[] = { - {"binary_format_major_version", - T_OBJECT, - offsetof(Metadata_obj, binary_format_major_version), - READONLY, - NULL}, - {"binary_format_minor_version", - T_OBJECT, - offsetof(Metadata_obj, binary_format_minor_version), - READONLY, - NULL}, - {"build_epoch", - T_OBJECT, - offsetof(Metadata_obj, build_epoch), - READONLY, - NULL}, - {"database_type", - T_OBJECT, - offsetof(Metadata_obj, database_type), - READONLY, - NULL}, - {"description", - T_OBJECT, - offsetof(Metadata_obj, description), - READONLY, - NULL}, - {"ip_version", - T_OBJECT, - offsetof(Metadata_obj, ip_version), - READONLY, - NULL}, - {"languages", T_OBJECT, offsetof(Metadata_obj, languages), READONLY, NULL}, - {"node_count", - T_OBJECT, - offsetof(Metadata_obj, node_count), - READONLY, - NULL}, - {"record_size", - T_OBJECT, - offsetof(Metadata_obj, record_size), - READONLY, - NULL}, - {NULL, 0, 0, 0, NULL}}; - -// clang-format off -static PyTypeObject Metadata_Type = { - PyVarObject_HEAD_INIT(NULL, 0) - .tp_basicsize = sizeof(Metadata_obj), - .tp_dealloc = Metadata_dealloc, - .tp_doc = "Metadata object", - .tp_flags = Py_TPFLAGS_DEFAULT, - .tp_members = Metadata_members, - .tp_methods = Metadata_methods, - .tp_name = "Metadata", - .tp_init = Metadata_init}; -// clang-format on - -static PyMethodDef MaxMindDB_methods[] = {{NULL, NULL, 0, NULL}}; - -static struct PyModuleDef MaxMindDB_module = { - PyModuleDef_HEAD_INIT, - .m_name = "extension", - .m_doc = "This is a C extension to read MaxMind DB file format", - .m_methods = MaxMindDB_methods, + +#define R_OFF(_x) offsetof(ReaderObject, _x) + +static PyMemberDef ReaderType_members[] = { + { "closed", T_OBJECT, R_OFF(closed), READONLY, NULL }, + { NULL } }; -PyMODINIT_FUNC PyInit_extension(void) { - PyObject *m; +static PyMethodDef MetadataType_methods[] = { + { NULL, NULL } +}; - m = PyModule_Create(&MaxMindDB_module); +#define M_OFF(_x) offsetof(MetadataObject, _x) + +static PyMemberDef MetadataType_members[] = { + { "binary_format_major_version", T_OBJECT, M_OFF(binary_format_major_version), READONLY, NULL }, + { "binary_format_minor_version", T_OBJECT, M_OFF(binary_format_minor_version), READONLY, NULL }, + { "build_epoch", T_OBJECT, M_OFF(build_epoch), READONLY, NULL }, + { "database_type", T_OBJECT, M_OFF(database_type), READONLY, NULL }, + { "description", T_OBJECT, M_OFF(description), READONLY, NULL }, + { "ip_version", T_OBJECT, M_OFF(ip_version), READONLY, NULL }, + { "languages", T_OBJECT, M_OFF(languages), READONLY, NULL }, + { "node_count", T_OBJECT, M_OFF(node_count), READONLY, NULL }, + { "record_size", T_OBJECT, M_OFF(record_size), READONLY, NULL }, + { NULL } +}; - if (!m) { - return NULL; - } +PyDoc_STRVAR(MetadataType_doc, +"MaxMindDB metadata\n" +"\n" +"Metadata objects contain useful metadata about the current MaxMindDB" +); + +static PyType_Slot MetadataType_slots[] = { + { Py_tp_doc, (char *)MetadataType_doc }, + { Py_tp_init, MetadataType_init }, + { Py_tp_dealloc, MetadataType_dealloc }, + { Py_tp_members, MetadataType_members }, + { Py_tp_methods, MetadataType_methods }, + { 0, NULL } +}; - Reader_Type.tp_new = PyType_GenericNew; - if (PyType_Ready(&Reader_Type)) { - return NULL; - } - Py_INCREF(&Reader_Type); - PyModule_AddObject(m, "Reader", (PyObject *)&Reader_Type); +PyDoc_STRVAR(ReaderType_doc, +"MaxMindDB reader\n" +"\n" +"Reader objects are responsible for reading and parsing MaxMindDB databases.\n" +); + +static PyType_Slot ReaderType_slots[] = { + { Py_tp_doc, (char *)ReaderType_doc }, + { Py_tp_init, ReaderType_init }, + { Py_tp_dealloc, ReaderType_dealloc }, + { Py_tp_members, ReaderType_members }, + { Py_tp_methods, ReaderType_methods }, + {0, NULL } +}; - Metadata_Type.tp_new = PyType_GenericNew; - if (PyType_Ready(&Metadata_Type)) { - return NULL; +static PyType_Spec MetadataType_spec = { + .name = "Metadata", + .basicsize = sizeof(MetadataObject), + .itemsize = 0, + .flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC, + .slots = MetadataType_slots, +}; + +static PyType_Spec ReaderType_spec = { + .name = "Reader", + .basicsize = sizeof(ReaderObject), + .itemsize = 0, + .flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC, + .slots = ReaderType_slots, +}; + +static int +maxminddb_exec(PyObject *m) +{ + _maxminddb_state *state = get_maxminddb_state(m); + + state->ReaderType = PyType_FromSpec(&ReaderType_spec); + if (state->ReaderType == NULL) { + return -1; + } + Py_INCREF(state->ReaderType); + if (PyModule_AddObject(m, "Reader", state->ReaderType) < 0) { + Py_DECREF(state->ReaderType); + return -1; } - PyModule_AddObject(m, "Metadata", (PyObject *)&Metadata_Type); - PyObject *error_mod = PyImport_ImportModule("maxminddb.errors"); - if (error_mod == NULL) { - return NULL; + state->MetadataType = PyType_FromSpec(&MetadataType_spec); + if (state->MetadataType == NULL) { + return -1; + } + Py_INCREF(state->MetadataType); + if (PyModule_AddObject(m, "Metadata", state->MetadataType) < 0) { + Py_DECREF(state->MetadataType); + return -1; } - MaxMindDB_error = PyObject_GetAttrString(error_mod, "InvalidDatabaseError"); - Py_DECREF(error_mod); + return 0; +} - if (MaxMindDB_error == NULL) { - return NULL; - } +static PyMethodDef maxminddb_methods[] = { + { NULL, NULL } +}; - Py_INCREF(MaxMindDB_error); +static PyModuleDef_Slot maxminddb_slots[] = { + { Py_mod_exec, maxminddb_exec }, + { 0, NULL } +}; - /* We primarily add it to the module for backwards compatibility */ - PyModule_AddObject(m, "InvalidDatabaseError", MaxMindDB_error); +static int +maxminddb_traverse(PyObject *m, visitproc visit, void *arg) +{ + _maxminddb_state *state = get_maxminddb_state(m); + Py_VISIT(state->ReaderType); + Py_VISIT(state->MetadataType); + return 0; +} + +static int +maxminddb_clear(PyObject *m) +{ + _maxminddb_state *state = get_maxminddb_state(m); + Py_CLEAR(state->ReaderType); + Py_CLEAR(state->MetadataType); + return 0; +} + +static void +maxminddb_free(void *m) +{ + maxminddb_clear((PyObject *)m); +} + +static struct PyModuleDef maxminddb_module = { + PyModuleDef_HEAD_INIT, + .m_name = "maxminddb", + .m_doc = maxminddb_module_doc, + .m_size = sizeof(_maxminddb_state), + .m_methods = maxminddb_methods, + .m_slots = maxminddb_slots, + .m_traverse = maxminddb_traverse, + .m_clear = maxminddb_clear, + .m_free = maxminddb_free, +}; - return m; +PyMODINIT_FUNC +PyInit_maxminddb(void) +{ + return PyModuleDef_Init(&maxminddb_module); }