1
2/* DBM module using dictionary interface */
3/* Author: Anthony Baxter, after dbmmodule.c */
4/* Doc strings: Mitch Chapman */
5
6
7#include "Python.h"
8
9#include <sys/types.h>
10#include <sys/stat.h>
11#include <fcntl.h>
12#include "gdbm.h"
13
14#if defined(WIN32) && !defined(__CYGWIN__)
15#include "gdbmerrno.h"
16extern const char * gdbm_strerror(gdbm_error);
17#endif
18
19PyDoc_STRVAR(gdbmmodule__doc__,
20"This module provides an interface to the GNU DBM (GDBM) library.\n\
21\n\
22This module is quite similar to the dbm module, but uses GDBM instead to\n\
23provide some additional functionality. Please note that the file formats\n\
24created by GDBM and dbm are incompatible. \n\
25\n\
26GDBM objects behave like mappings (dictionaries), except that keys and\n\
27values are always strings. Printing a GDBM object doesn't print the\n\
28keys and values, and the items() and values() methods are not\n\
29supported.");
30
31typedef struct {
32    PyObject_HEAD
33    int di_size;	/* -1 means recompute */
34    GDBM_FILE di_dbm;
35} dbmobject;
36
37static PyTypeObject Dbmtype;
38
39#define is_dbmobject(v) (Py_TYPE(v) == &Dbmtype)
40#define check_dbmobject_open(v) if ((v)->di_dbm == NULL) \
41    { PyErr_SetString(DbmError, "GDBM object has already been closed"); \
42      return NULL; }
43
44
45
46static PyObject *DbmError;
47
48PyDoc_STRVAR(gdbm_object__doc__,
49"This object represents a GDBM database.\n\
50GDBM objects behave like mappings (dictionaries), except that keys and\n\
51values are always strings. Printing a GDBM object doesn't print the\n\
52keys and values, and the items() and values() methods are not\n\
53supported.\n\
54\n\
55GDBM objects also support additional operations such as firstkey,\n\
56nextkey, reorganize, and sync.");
57
58static PyObject *
59newdbmobject(char *file, int flags, int mode)
60{
61    dbmobject *dp;
62
63    dp = PyObject_New(dbmobject, &Dbmtype);
64    if (dp == NULL)
65        return NULL;
66    dp->di_size = -1;
67    errno = 0;
68    if ((dp->di_dbm = gdbm_open(file, 0, flags, mode, NULL)) == 0) {
69        if (errno != 0)
70            PyErr_SetFromErrno(DbmError);
71        else
72            PyErr_SetString(DbmError, gdbm_strerror(gdbm_errno));
73        Py_DECREF(dp);
74        return NULL;
75    }
76    return (PyObject *)dp;
77}
78
79/* Methods */
80
81static void
82dbm_dealloc(register dbmobject *dp)
83{
84    if (dp->di_dbm)
85        gdbm_close(dp->di_dbm);
86    PyObject_Del(dp);
87}
88
89static Py_ssize_t
90dbm_length(dbmobject *dp)
91{
92    if (dp->di_dbm == NULL) {
93        PyErr_SetString(DbmError, "GDBM object has already been closed");
94        return -1;
95    }
96    if (dp->di_size < 0) {
97        datum key,okey;
98        int size;
99        okey.dsize=0;
100        okey.dptr=NULL;
101
102        size = 0;
103        for (key=gdbm_firstkey(dp->di_dbm); key.dptr;
104             key = gdbm_nextkey(dp->di_dbm,okey)) {
105            size++;
106            if(okey.dsize) free(okey.dptr);
107            okey=key;
108        }
109        dp->di_size = size;
110    }
111    return dp->di_size;
112}
113
114static PyObject *
115dbm_subscript(dbmobject *dp, register PyObject *key)
116{
117    PyObject *v;
118    datum drec, krec;
119
120    if (!PyArg_Parse(key, "s#", &krec.dptr, &krec.dsize) )
121        return NULL;
122
123    if (dp->di_dbm == NULL) {
124        PyErr_SetString(DbmError,
125                        "GDBM object has already been closed");
126        return NULL;
127    }
128    drec = gdbm_fetch(dp->di_dbm, krec);
129    if (drec.dptr == 0) {
130        PyErr_SetString(PyExc_KeyError,
131                        PyString_AS_STRING((PyStringObject *)key));
132        return NULL;
133    }
134    v = PyString_FromStringAndSize(drec.dptr, drec.dsize);
135    free(drec.dptr);
136    return v;
137}
138
139static int
140dbm_ass_sub(dbmobject *dp, PyObject *v, PyObject *w)
141{
142    datum krec, drec;
143
144    if (!PyArg_Parse(v, "s#", &krec.dptr, &krec.dsize) ) {
145        PyErr_SetString(PyExc_TypeError,
146                        "gdbm mappings have string indices only");
147        return -1;
148    }
149    if (dp->di_dbm == NULL) {
150        PyErr_SetString(DbmError,
151                        "GDBM object has already been closed");
152        return -1;
153    }
154    dp->di_size = -1;
155    if (w == NULL) {
156        if (gdbm_delete(dp->di_dbm, krec) < 0) {
157            PyErr_SetString(PyExc_KeyError,
158                            PyString_AS_STRING((PyStringObject *)v));
159            return -1;
160        }
161    }
162    else {
163        if (!PyArg_Parse(w, "s#", &drec.dptr, &drec.dsize)) {
164            PyErr_SetString(PyExc_TypeError,
165                            "gdbm mappings have string elements only");
166            return -1;
167        }
168        errno = 0;
169        if (gdbm_store(dp->di_dbm, krec, drec, GDBM_REPLACE) < 0) {
170            if (errno != 0)
171                PyErr_SetFromErrno(DbmError);
172            else
173                PyErr_SetString(DbmError,
174                                gdbm_strerror(gdbm_errno));
175            return -1;
176        }
177    }
178    return 0;
179}
180
181static int
182dbm_contains(register dbmobject *dp, PyObject *arg)
183{
184    datum key;
185
186    if ((dp)->di_dbm == NULL) {
187        PyErr_SetString(DbmError,
188                        "GDBM object has already been closed");
189        return -1;
190    }
191    if (!PyString_Check(arg)) {
192        PyErr_Format(PyExc_TypeError,
193                     "gdbm key must be string, not %.100s",
194                     arg->ob_type->tp_name);
195        return -1;
196    }
197    key.dptr = PyString_AS_STRING(arg);
198    key.dsize = PyString_GET_SIZE(arg);
199    return gdbm_exists(dp->di_dbm, key);
200}
201
202static PySequenceMethods dbm_as_sequence = {
203    (lenfunc)dbm_length,               /*_length*/
204       0,              /*sq_concat*/
205       0,                      /*sq_repeat*/
206       0,                      /*sq_item*/
207       0,                      /*sq_slice*/
208       0,                      /*sq_ass_item*/
209       0,                  /*sq_ass_slice*/
210       (objobjproc)dbm_contains,               /*sq_contains*/
211       0,                  /*sq_inplace_concat*/
212       0                   /*sq_inplace_repeat*/
213};
214
215static PyMappingMethods dbm_as_mapping = {
216    (lenfunc)dbm_length,		/*mp_length*/
217    (binaryfunc)dbm_subscript,          /*mp_subscript*/
218    (objobjargproc)dbm_ass_sub,         /*mp_ass_subscript*/
219};
220
221PyDoc_STRVAR(dbm_close__doc__,
222"close() -> None\n\
223Closes the database.");
224
225static PyObject *
226dbm_close(register dbmobject *dp, PyObject *unused)
227{
228    if (dp->di_dbm)
229        gdbm_close(dp->di_dbm);
230    dp->di_dbm = NULL;
231    Py_INCREF(Py_None);
232    return Py_None;
233}
234
235PyDoc_STRVAR(dbm_keys__doc__,
236"keys() -> list_of_keys\n\
237Get a list of all keys in the database.");
238
239static PyObject *
240dbm_keys(register dbmobject *dp, PyObject *unused)
241{
242    register PyObject *v, *item;
243    datum key, nextkey;
244    int err;
245
246    if (dp == NULL || !is_dbmobject(dp)) {
247        PyErr_BadInternalCall();
248        return NULL;
249    }
250    check_dbmobject_open(dp);
251
252    v = PyList_New(0);
253    if (v == NULL)
254        return NULL;
255
256    key = gdbm_firstkey(dp->di_dbm);
257    while (key.dptr) {
258        item = PyString_FromStringAndSize(key.dptr, key.dsize);
259        if (item == NULL) {
260            free(key.dptr);
261            Py_DECREF(v);
262            return NULL;
263        }
264        err = PyList_Append(v, item);
265        Py_DECREF(item);
266        if (err != 0) {
267            free(key.dptr);
268            Py_DECREF(v);
269            return NULL;
270        }
271        nextkey = gdbm_nextkey(dp->di_dbm, key);
272        free(key.dptr);
273        key = nextkey;
274    }
275    return v;
276}
277
278PyDoc_STRVAR(dbm_has_key__doc__,
279"has_key(key) -> boolean\n\
280Find out whether or not the database contains a given key.");
281
282static PyObject *
283dbm_has_key(register dbmobject *dp, PyObject *args)
284{
285    datum key;
286
287    if (!PyArg_ParseTuple(args, "s#:has_key", &key.dptr, &key.dsize))
288        return NULL;
289    check_dbmobject_open(dp);
290    return PyInt_FromLong((long) gdbm_exists(dp->di_dbm, key));
291}
292
293PyDoc_STRVAR(dbm_firstkey__doc__,
294"firstkey() -> key\n\
295It's possible to loop over every key in the database using this method\n\
296and the nextkey() method. The traversal is ordered by GDBM's internal\n\
297hash values, and won't be sorted by the key values. This method\n\
298returns the starting key.");
299
300static PyObject *
301dbm_firstkey(register dbmobject *dp, PyObject *unused)
302{
303    register PyObject *v;
304    datum key;
305
306    check_dbmobject_open(dp);
307    key = gdbm_firstkey(dp->di_dbm);
308    if (key.dptr) {
309        v = PyString_FromStringAndSize(key.dptr, key.dsize);
310        free(key.dptr);
311        return v;
312    }
313    else {
314        Py_INCREF(Py_None);
315        return Py_None;
316    }
317}
318
319PyDoc_STRVAR(dbm_nextkey__doc__,
320"nextkey(key) -> next_key\n\
321Returns the key that follows key in the traversal.\n\
322The following code prints every key in the database db, without having\n\
323to create a list in memory that contains them all:\n\
324\n\
325      k = db.firstkey()\n\
326      while k != None:\n\
327          print k\n\
328          k = db.nextkey(k)");
329
330static PyObject *
331dbm_nextkey(register dbmobject *dp, PyObject *args)
332{
333    register PyObject *v;
334    datum key, nextkey;
335
336    if (!PyArg_ParseTuple(args, "s#:nextkey", &key.dptr, &key.dsize))
337        return NULL;
338    check_dbmobject_open(dp);
339    nextkey = gdbm_nextkey(dp->di_dbm, key);
340    if (nextkey.dptr) {
341        v = PyString_FromStringAndSize(nextkey.dptr, nextkey.dsize);
342        free(nextkey.dptr);
343        return v;
344    }
345    else {
346        Py_INCREF(Py_None);
347        return Py_None;
348    }
349}
350
351PyDoc_STRVAR(dbm_reorganize__doc__,
352"reorganize() -> None\n\
353If you have carried out a lot of deletions and would like to shrink\n\
354the space used by the GDBM file, this routine will reorganize the\n\
355database. GDBM will not shorten the length of a database file except\n\
356by using this reorganization; otherwise, deleted file space will be\n\
357kept and reused as new (key,value) pairs are added.");
358
359static PyObject *
360dbm_reorganize(register dbmobject *dp, PyObject *unused)
361{
362    check_dbmobject_open(dp);
363    errno = 0;
364    if (gdbm_reorganize(dp->di_dbm) < 0) {
365        if (errno != 0)
366            PyErr_SetFromErrno(DbmError);
367        else
368            PyErr_SetString(DbmError, gdbm_strerror(gdbm_errno));
369        return NULL;
370    }
371    Py_INCREF(Py_None);
372    return Py_None;
373}
374
375PyDoc_STRVAR(dbm_sync__doc__,
376"sync() -> None\n\
377When the database has been opened in fast mode, this method forces\n\
378any unwritten data to be written to the disk.");
379
380static PyObject *
381dbm_sync(register dbmobject *dp, PyObject *unused)
382{
383    check_dbmobject_open(dp);
384    gdbm_sync(dp->di_dbm);
385    Py_INCREF(Py_None);
386    return Py_None;
387}
388
389static PyMethodDef dbm_methods[] = {
390    {"close",	  (PyCFunction)dbm_close,   METH_NOARGS, dbm_close__doc__},
391    {"keys",	  (PyCFunction)dbm_keys,    METH_NOARGS, dbm_keys__doc__},
392    {"has_key",   (PyCFunction)dbm_has_key, METH_VARARGS, dbm_has_key__doc__},
393    {"firstkey",  (PyCFunction)dbm_firstkey,METH_NOARGS, dbm_firstkey__doc__},
394    {"nextkey",	  (PyCFunction)dbm_nextkey, METH_VARARGS, dbm_nextkey__doc__},
395    {"reorganize",(PyCFunction)dbm_reorganize,METH_NOARGS, dbm_reorganize__doc__},
396    {"sync",      (PyCFunction)dbm_sync,    METH_NOARGS, dbm_sync__doc__},
397    {NULL,		NULL}		/* sentinel */
398};
399
400static PyObject *
401dbm_getattr(dbmobject *dp, char *name)
402{
403    return Py_FindMethod(dbm_methods, (PyObject *)dp, name);
404}
405
406static PyTypeObject Dbmtype = {
407    PyVarObject_HEAD_INIT(0, 0)
408    "gdbm.gdbm",
409    sizeof(dbmobject),
410    0,
411    (destructor)dbm_dealloc,            /*tp_dealloc*/
412    0,                                  /*tp_print*/
413    (getattrfunc)dbm_getattr,           /*tp_getattr*/
414    0,                                  /*tp_setattr*/
415    0,                                  /*tp_compare*/
416    0,                                  /*tp_repr*/
417    0,                                  /*tp_as_number*/
418    &dbm_as_sequence,                   /*tp_as_sequence*/
419    &dbm_as_mapping,                    /*tp_as_mapping*/
420    0,                                  /*tp_hash*/
421    0,                                  /*tp_call*/
422    0,                                  /*tp_str*/
423    0,                                  /*tp_getattro*/
424    0,                                  /*tp_setattro*/
425    0,                                  /*tp_as_buffer*/
426    Py_TPFLAGS_DEFAULT,                 /*tp_xxx4*/
427    gdbm_object__doc__,                 /*tp_doc*/
428};
429
430/* ----------------------------------------------------------------- */
431
432PyDoc_STRVAR(dbmopen__doc__,
433"open(filename, [flags, [mode]])  -> dbm_object\n\
434Open a dbm database and return a dbm object. The filename argument is\n\
435the name of the database file.\n\
436\n\
437The optional flags argument can be 'r' (to open an existing database\n\
438for reading only -- default), 'w' (to open an existing database for\n\
439reading and writing), 'c' (which creates the database if it doesn't\n\
440exist), or 'n' (which always creates a new empty database).\n\
441\n\
442Some versions of gdbm support additional flags which must be\n\
443appended to one of the flags described above. The module constant\n\
444'open_flags' is a string of valid additional flags. The 'f' flag\n\
445opens the database in fast mode; altered data will not automatically\n\
446be written to the disk after every change. This results in faster\n\
447writes to the database, but may result in an inconsistent database\n\
448if the program crashes while the database is still open. Use the\n\
449sync() method to force any unwritten data to be written to the disk.\n\
450The 's' flag causes all database operations to be synchronized to\n\
451disk. The 'u' flag disables locking of the database file.\n\
452\n\
453The optional mode argument is the Unix mode of the file, used only\n\
454when the database has to be created. It defaults to octal 0666. ");
455
456static PyObject *
457dbmopen(PyObject *self, PyObject *args)
458{
459    char *name;
460    char *flags = "r";
461    int iflags;
462    int mode = 0666;
463
464    if (!PyArg_ParseTuple(args, "s|si:open", &name, &flags, &mode))
465        return NULL;
466    switch (flags[0]) {
467    case 'r':
468        iflags = GDBM_READER;
469        break;
470    case 'w':
471        iflags = GDBM_WRITER;
472        break;
473    case 'c':
474        iflags = GDBM_WRCREAT;
475        break;
476    case 'n':
477        iflags = GDBM_NEWDB;
478        break;
479    default:
480        PyErr_SetString(DbmError,
481                        "First flag must be one of 'r', 'w', 'c' or 'n'");
482        return NULL;
483    }
484    for (flags++; *flags != '\0'; flags++) {
485        char buf[40];
486        switch (*flags) {
487#ifdef GDBM_FAST
488            case 'f':
489                iflags |= GDBM_FAST;
490                break;
491#endif
492#ifdef GDBM_SYNC
493            case 's':
494                iflags |= GDBM_SYNC;
495                break;
496#endif
497#ifdef GDBM_NOLOCK
498            case 'u':
499                iflags |= GDBM_NOLOCK;
500                break;
501#endif
502            default:
503                PyOS_snprintf(buf, sizeof(buf), "Flag '%c' is not supported.",
504                	      *flags);
505                PyErr_SetString(DbmError, buf);
506                return NULL;
507        }
508    }
509
510    return newdbmobject(name, iflags, mode);
511}
512
513static char dbmmodule_open_flags[] = "rwcn"
514#ifdef GDBM_FAST
515                                     "f"
516#endif
517#ifdef GDBM_SYNC
518                                     "s"
519#endif
520#ifdef GDBM_NOLOCK
521                                     "u"
522#endif
523                                     ;
524
525static PyMethodDef dbmmodule_methods[] = {
526    { "open", (PyCFunction)dbmopen, METH_VARARGS, dbmopen__doc__},
527    { 0, 0 },
528};
529
530PyMODINIT_FUNC
531initgdbm(void) {
532    PyObject *m, *d, *s;
533
534    Dbmtype.ob_type = &PyType_Type;
535    m = Py_InitModule4("gdbm", dbmmodule_methods,
536                       gdbmmodule__doc__, (PyObject *)NULL,
537                       PYTHON_API_VERSION);
538    if (m == NULL)
539	return;
540    d = PyModule_GetDict(m);
541    DbmError = PyErr_NewException("gdbm.error", NULL, NULL);
542    if (DbmError != NULL) {
543        PyDict_SetItemString(d, "error", DbmError);
544        s = PyString_FromString(dbmmodule_open_flags);
545        PyDict_SetItemString(d, "open_flags", s);
546        Py_DECREF(s);
547    }
548}
549