#include "Python.h"
#include "gdbm.h"
#include <fcntl.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <sys/types.h>
#if defined(WIN32) && !defined(__CYGWIN__)
#include "gdbmerrno.h"
extern const char * gdbm_strerror(gdbm_error);
#endif
typedef struct {
PyTypeObject *gdbm_type;
PyObject *gdbm_error;
} _gdbm_state;
static inline _gdbm_state*
get_gdbm_state(PyObject *module)
{
void *state = PyModule_GetState(module);
assert(state != NULL);
return (_gdbm_state *)state;
}
PyDoc_STRVAR(gdbmmodule__doc__,
"This module provides an interface to the GNU DBM (GDBM) library.\n\
\n\
This module is quite similar to the dbm module, but uses GDBM instead to\n\
provide some additional functionality. Please note that the file formats\n\
created by GDBM and dbm are incompatible.\n\
\n\
GDBM objects behave like mappings (dictionaries), except that keys and\n\
values are always immutable bytes-like objects or strings. Printing\n\
a GDBM object doesn't print the keys and values, and the items() and\n\
values() methods are not supported.");
typedef struct {
PyObject_HEAD
Py_ssize_t di_size;
GDBM_FILE di_dbm;
} gdbmobject;
#include "clinic/_gdbmmodule.c.h"
#define check_gdbmobject_open(v, err) \
if ((v)->di_dbm == NULL) { \
PyErr_SetString(err, "GDBM object has already been closed"); \
return NULL; \
}
PyDoc_STRVAR(gdbm_object__doc__,
"This object represents a GDBM database.\n\
GDBM objects behave like mappings (dictionaries), except that keys and\n\
values are always immutable bytes-like objects or strings. Printing\n\
a GDBM object doesn't print the keys and values, and the items() and\n\
values() methods are not supported.\n\
\n\
GDBM objects also support additional operations such as firstkey,\n\
nextkey, reorganize, and sync.");
static PyObject *
newgdbmobject(_gdbm_state *state, const char *file, int flags, int mode)
{
gdbmobject *dp = PyObject_GC_New(gdbmobject, state->gdbm_type);
if (dp == NULL) {
return NULL;
}
dp->di_size = -1;
errno = 0;
PyObject_GC_Track(dp);
if ((dp->di_dbm = gdbm_open((char *)file, 0, flags, mode, NULL)) == 0) {
if (errno != 0) {
PyErr_SetFromErrnoWithFilename(state->gdbm_error, file);
}
else {
PyErr_SetString(state->gdbm_error, gdbm_strerror(gdbm_errno));
}
Py_DECREF(dp);
return NULL;
}
return (PyObject *)dp;
}
static int
gdbm_traverse(gdbmobject *dp, visitproc visit, void *arg)
{
Py_VISIT(Py_TYPE(dp));
return 0;
}
static void
gdbm_dealloc(gdbmobject *dp)
{
PyObject_GC_UnTrack(dp);
if (dp->di_dbm) {
gdbm_close(dp->di_dbm);
}
PyTypeObject *tp = Py_TYPE(dp);
tp->tp_free(dp);
Py_DECREF(tp);
}
static Py_ssize_t
gdbm_length(gdbmobject *dp)
{
_gdbm_state *state = PyType_GetModuleState(Py_TYPE(dp));
if (dp->di_dbm == NULL) {
PyErr_SetString(state->gdbm_error, "GDBM object has already been closed");
return -1;
}
if (dp->di_size < 0) {
#if GDBM_VERSION_MAJOR >= 1 && GDBM_VERSION_MINOR >= 11
errno = 0;
gdbm_count_t count;
if (gdbm_count(dp->di_dbm, &count) == -1) {
if (errno != 0) {
PyErr_SetFromErrno(state->gdbm_error);
}
else {
PyErr_SetString(state->gdbm_error, gdbm_strerror(gdbm_errno));
}
return -1;
}
if (count > PY_SSIZE_T_MAX) {
PyErr_SetString(PyExc_OverflowError, "count exceeds PY_SSIZE_T_MAX");
return -1;
}
dp->di_size = count;
#else
datum key,okey;
okey.dsize=0;
okey.dptr=NULL;
Py_ssize_t size = 0;
for (key = gdbm_firstkey(dp->di_dbm); key.dptr;
key = gdbm_nextkey(dp->di_dbm,okey)) {
size++;
if (okey.dsize) {
free(okey.dptr);
}
okey=key;
}
dp->di_size = size;
#endif
}
return dp->di_size;
}
static int
gdbm_bool(gdbmobject *dp)
{
_gdbm_state *state = PyType_GetModuleState(Py_TYPE(dp));
if (dp->di_dbm == NULL) {
PyErr_SetString(state->gdbm_error, "GDBM object has already been closed");
return -1;
}
if (dp->di_size > 0) {
return 1;
}
if (dp->di_size == 0) {
return 0;
}
datum key = gdbm_firstkey(dp->di_dbm);
if (key.dptr == NULL) {
dp->di_size = 0;
return 0;
}
free(key.dptr);
return 1;
}
static int
parse_datum(PyObject *o, datum *d, const char *failmsg)
{
Py_ssize_t size;
if (!PyArg_Parse(o, "s#", &d->dptr, &size)) {
if (failmsg != NULL) {
PyErr_SetString(PyExc_TypeError, failmsg);
}
return 0;
}
if (INT_MAX < size) {
PyErr_SetString(PyExc_OverflowError, "size does not fit in an int");
return 0;
}
d->dsize = size;
return 1;
}
static PyObject *
gdbm_subscript(gdbmobject *dp, PyObject *key)
{
PyObject *v;
datum drec, krec;
_gdbm_state *state = PyType_GetModuleState(Py_TYPE(dp));
if (!parse_datum(key, &krec, NULL)) {
return NULL;
}
if (dp->di_dbm == NULL) {
PyErr_SetString(state->gdbm_error,
"GDBM object has already been closed");
return NULL;
}
drec = gdbm_fetch(dp->di_dbm, krec);
if (drec.dptr == 0) {
PyErr_SetObject(PyExc_KeyError, key);
return NULL;
}
v = PyBytes_FromStringAndSize(drec.dptr, drec.dsize);
free(drec.dptr);
return v;
}
static PyObject *
_gdbm_gdbm_get_impl(gdbmobject *self, PyObject *key, PyObject *default_value)
{
PyObject *res;
res = gdbm_subscript(self, key);
if (res == NULL && PyErr_ExceptionMatches(PyExc_KeyError)) {
PyErr_Clear();
return Py_NewRef(default_value);
}
return res;
}
static int
gdbm_ass_sub(gdbmobject *dp, PyObject *v, PyObject *w)
{
datum krec, drec;
const char *failmsg = "gdbm mappings have bytes or string indices only";
_gdbm_state *state = PyType_GetModuleState(Py_TYPE(dp));
if (!parse_datum(v, &krec, failmsg)) {
return -1;
}
if (dp->di_dbm == NULL) {
PyErr_SetString(state->gdbm_error,
"GDBM object has already been closed");
return -1;
}
dp->di_size = -1;
if (w == NULL) {
if (gdbm_delete(dp->di_dbm, krec) < 0) {
if (gdbm_errno == GDBM_ITEM_NOT_FOUND) {
PyErr_SetObject(PyExc_KeyError, v);
}
else {
PyErr_SetString(state->gdbm_error, gdbm_strerror(gdbm_errno));
}
return -1;
}
}
else {
if (!parse_datum(w, &drec, failmsg)) {
return -1;
}
errno = 0;
if (gdbm_store(dp->di_dbm, krec, drec, GDBM_REPLACE) < 0) {
if (errno != 0)
PyErr_SetFromErrno(state->gdbm_error);
else
PyErr_SetString(state->gdbm_error,
gdbm_strerror(gdbm_errno));
return -1;
}
}
return 0;
}
static PyObject *
_gdbm_gdbm_setdefault_impl(gdbmobject *self, PyObject *key,
PyObject *default_value)
{
PyObject *res;
res = gdbm_subscript(self, key);
if (res == NULL && PyErr_ExceptionMatches(PyExc_KeyError)) {
PyErr_Clear();
if (gdbm_ass_sub(self, key, default_value) < 0)
return NULL;
return gdbm_subscript(self, key);
}
return res;
}
static PyObject *
_gdbm_gdbm_close_impl(gdbmobject *self)
{
if (self->di_dbm) {
gdbm_close(self->di_dbm);
}
self->di_dbm = NULL;
Py_RETURN_NONE;
}
static PyObject *
_gdbm_gdbm_keys_impl(gdbmobject *self, PyTypeObject *cls)
{
PyObject *v, *item;
datum key, nextkey;
int err;
_gdbm_state *state = PyType_GetModuleState(cls);
assert(state != NULL);
if (self == NULL || !Py_IS_TYPE(self, state->gdbm_type)) {
PyErr_BadInternalCall();
return NULL;
}
check_gdbmobject_open(self, state->gdbm_error);
v = PyList_New(0);
if (v == NULL)
return NULL;
key = gdbm_firstkey(self->di_dbm);
while (key.dptr) {
item = PyBytes_FromStringAndSize(key.dptr, key.dsize);
if (item == NULL) {
free(key.dptr);
Py_DECREF(v);
return NULL;
}
err = PyList_Append(v, item);
Py_DECREF(item);
if (err != 0) {
free(key.dptr);
Py_DECREF(v);
return NULL;
}
nextkey = gdbm_nextkey(self->di_dbm, key);
free(key.dptr);
key = nextkey;
}
return v;
}
static int
gdbm_contains(PyObject *self, PyObject *arg)
{
gdbmobject *dp = (gdbmobject *)self;
datum key;
Py_ssize_t size;
_gdbm_state *state = PyType_GetModuleState(Py_TYPE(dp));
if ((dp)->di_dbm == NULL) {
PyErr_SetString(state->gdbm_error,
"GDBM object has already been closed");
return -1;
}
if (PyUnicode_Check(arg)) {
key.dptr = (char *)PyUnicode_AsUTF8AndSize(arg, &size);
key.dsize = size;
if (key.dptr == NULL)
return -1;
}
else if (!PyBytes_Check(arg)) {
PyErr_Format(PyExc_TypeError,
"gdbm key must be bytes or string, not %.100s",
Py_TYPE(arg)->tp_name);
return -1;
}
else {
key.dptr = PyBytes_AS_STRING(arg);
key.dsize = PyBytes_GET_SIZE(arg);
}
return gdbm_exists(dp->di_dbm, key);
}
static PyObject *
_gdbm_gdbm_firstkey_impl(gdbmobject *self, PyTypeObject *cls)
{
PyObject *v;
datum key;
_gdbm_state *state = PyType_GetModuleState(cls);
assert(state != NULL);
check_gdbmobject_open(self, state->gdbm_error);
key = gdbm_firstkey(self->di_dbm);
if (key.dptr) {
v = PyBytes_FromStringAndSize(key.dptr, key.dsize);
free(key.dptr);
return v;
}
else {
Py_RETURN_NONE;
}
}
static PyObject *
_gdbm_gdbm_nextkey_impl(gdbmobject *self, PyTypeObject *cls, const char *key,
Py_ssize_t key_length)
{
PyObject *v;
datum dbm_key, nextkey;
_gdbm_state *state = PyType_GetModuleState(cls);
assert(state != NULL);
dbm_key.dptr = (char *)key;
dbm_key.dsize = key_length;
check_gdbmobject_open(self, state->gdbm_error);
nextkey = gdbm_nextkey(self->di_dbm, dbm_key);
if (nextkey.dptr) {
v = PyBytes_FromStringAndSize(nextkey.dptr, nextkey.dsize);
free(nextkey.dptr);
return v;
}
else {
Py_RETURN_NONE;
}
}
static PyObject *
_gdbm_gdbm_reorganize_impl(gdbmobject *self, PyTypeObject *cls)
{
_gdbm_state *state = PyType_GetModuleState(cls);
assert(state != NULL);
check_gdbmobject_open(self, state->gdbm_error);
errno = 0;
if (gdbm_reorganize(self->di_dbm) < 0) {
if (errno != 0)
PyErr_SetFromErrno(state->gdbm_error);
else
PyErr_SetString(state->gdbm_error, gdbm_strerror(gdbm_errno));
return NULL;
}
Py_RETURN_NONE;
}
static PyObject *
_gdbm_gdbm_sync_impl(gdbmobject *self, PyTypeObject *cls)
{
_gdbm_state *state = PyType_GetModuleState(cls);
assert(state != NULL);
check_gdbmobject_open(self, state->gdbm_error);
gdbm_sync(self->di_dbm);
Py_RETURN_NONE;
}
static PyObject *
gdbm__enter__(PyObject *self, PyObject *args)
{
return Py_NewRef(self);
}
static PyObject *
gdbm__exit__(PyObject *self, PyObject *args)
{
return _gdbm_gdbm_close_impl((gdbmobject *)self);
}
static PyMethodDef gdbm_methods[] = {
_GDBM_GDBM_CLOSE_METHODDEF
_GDBM_GDBM_KEYS_METHODDEF
_GDBM_GDBM_FIRSTKEY_METHODDEF
_GDBM_GDBM_NEXTKEY_METHODDEF
_GDBM_GDBM_REORGANIZE_METHODDEF
_GDBM_GDBM_SYNC_METHODDEF
_GDBM_GDBM_GET_METHODDEF
_GDBM_GDBM_SETDEFAULT_METHODDEF
{"__enter__", gdbm__enter__, METH_NOARGS, NULL},
{"__exit__", gdbm__exit__, METH_VARARGS, NULL},
{NULL, NULL}
};
static PyType_Slot gdbmtype_spec_slots[] = {
{Py_tp_dealloc, gdbm_dealloc},
{Py_tp_traverse, gdbm_traverse},
{Py_tp_methods, gdbm_methods},
{Py_sq_contains, gdbm_contains},
{Py_mp_length, gdbm_length},
{Py_mp_subscript, gdbm_subscript},
{Py_mp_ass_subscript, gdbm_ass_sub},
{Py_nb_bool, gdbm_bool},
{Py_tp_doc, (char*)gdbm_object__doc__},
{0, 0}
};
static PyType_Spec gdbmtype_spec = {
.name = "_gdbm.gdbm",
.basicsize = sizeof(gdbmobject),
.flags = (Py_TPFLAGS_DEFAULT | Py_TPFLAGS_DISALLOW_INSTANTIATION |
Py_TPFLAGS_HAVE_GC | Py_TPFLAGS_IMMUTABLETYPE),
.slots = gdbmtype_spec_slots,
};
static PyObject *
dbmopen_impl(PyObject *module, PyObject *filename, const char *flags,
int mode)
{
int iflags;
_gdbm_state *state = get_gdbm_state(module);
assert(state != NULL);
switch (flags[0]) {
case 'r':
iflags = GDBM_READER;
break;
case 'w':
iflags = GDBM_WRITER;
break;
case 'c':
iflags = GDBM_WRCREAT;
break;
case 'n':
iflags = GDBM_NEWDB;
break;
default:
PyErr_SetString(state->gdbm_error,
"First flag must be one of 'r', 'w', 'c' or 'n'");
return NULL;
}
for (flags++; *flags != '\0'; flags++) {
switch (*flags) {
#ifdef GDBM_FAST
case 'f':
iflags |= GDBM_FAST;
break;
#endif
#ifdef GDBM_SYNC
case 's':
iflags |= GDBM_SYNC;
break;
#endif
#ifdef GDBM_NOLOCK
case 'u':
iflags |= GDBM_NOLOCK;
break;
#endif
default:
PyErr_Format(state->gdbm_error,
"Flag '%c' is not supported.", (unsigned char)*flags);
return NULL;
}
}
PyObject *filenamebytes;
if (!PyUnicode_FSConverter(filename, &filenamebytes)) {
return NULL;
}
const char *name = PyBytes_AS_STRING(filenamebytes);
if (strlen(name) != (size_t)PyBytes_GET_SIZE(filenamebytes)) {
Py_DECREF(filenamebytes);
PyErr_SetString(PyExc_ValueError, "embedded null character");
return NULL;
}
PyObject *self = newgdbmobject(state, name, iflags, mode);
Py_DECREF(filenamebytes);
return self;
}
static const char gdbmmodule_open_flags[] = "rwcn"
#ifdef GDBM_FAST
"f"
#endif
#ifdef GDBM_SYNC
"s"
#endif
#ifdef GDBM_NOLOCK
"u"
#endif
;
static PyMethodDef _gdbm_module_methods[] = {
DBMOPEN_METHODDEF
{ 0, 0 },
};
static int
_gdbm_exec(PyObject *module)
{
_gdbm_state *state = get_gdbm_state(module);
state->gdbm_type = (PyTypeObject *)PyType_FromModuleAndSpec(module,
&gdbmtype_spec, NULL);
if (state->gdbm_type == NULL) {
return -1;
}
state->gdbm_error = PyErr_NewException("_gdbm.error", PyExc_OSError, NULL);
if (state->gdbm_error == NULL) {
return -1;
}
if (PyModule_AddType(module, (PyTypeObject *)state->gdbm_error) < 0) {
return -1;
}
if (PyModule_AddStringConstant(module, "open_flags",
gdbmmodule_open_flags) < 0) {
return -1;
}
#if defined(GDBM_VERSION_MAJOR) && defined(GDBM_VERSION_MINOR) && \
defined(GDBM_VERSION_PATCH)
PyObject *obj = Py_BuildValue("iii", GDBM_VERSION_MAJOR,
GDBM_VERSION_MINOR, GDBM_VERSION_PATCH);
if (obj == NULL) {
return -1;
}
if (PyModule_AddObject(module, "_GDBM_VERSION", obj) < 0) {
Py_DECREF(obj);
return -1;
}
#endif
return 0;
}
static int
_gdbm_module_traverse(PyObject *module, visitproc visit, void *arg)
{
_gdbm_state *state = get_gdbm_state(module);
Py_VISIT(state->gdbm_error);
Py_VISIT(state->gdbm_type);
return 0;
}
static int
_gdbm_module_clear(PyObject *module)
{
_gdbm_state *state = get_gdbm_state(module);
Py_CLEAR(state->gdbm_error);
Py_CLEAR(state->gdbm_type);
return 0;
}
static void
_gdbm_module_free(void *module)
{
_gdbm_module_clear((PyObject *)module);
}
static PyModuleDef_Slot _gdbm_module_slots[] = {
{Py_mod_exec, _gdbm_exec},
{Py_mod_multiple_interpreters, Py_MOD_PER_INTERPRETER_GIL_SUPPORTED},
{0, NULL}
};
static struct PyModuleDef _gdbmmodule = {
PyModuleDef_HEAD_INIT,
.m_name = "_gdbm",
.m_doc = gdbmmodule__doc__,
.m_size = sizeof(_gdbm_state),
.m_methods = _gdbm_module_methods,
.m_slots = _gdbm_module_slots,
.m_traverse = _gdbm_module_traverse,
.m_clear = _gdbm_module_clear,
.m_free = _gdbm_module_free,
};
PyMODINIT_FUNC
PyInit__gdbm(void)
{
return PyModuleDef_Init(&_gdbmmodule);
}