Commits

Mikhail Korobov committed adaa050

Bundle tinycdb library for easier installation

Comments (0)

Files changed (41)

+#! /usr/bin/env python
+
+import os
 from distutils.core import setup
 from distutils.extension import Extension
 from Cython.Distutils import build_ext
 
+TINYCDB_DIR = 'src'
+TINYCDB_FILE_NAMES = ['cdb_init.c', 'cdb_find.c', 'cdb_findnext.c', 'cdb_seq.c', 'cdb_seek.c', 'cdb_unpack.c',
+                      'cdb_make_add.c', 'cdb_make_put.c', 'cdb_make.c', 'cdb_hash.c']
+TINYCDB_FILES = [os.path.join(TINYCDB_DIR, name) for name in TINYCDB_FILE_NAMES]
+
 setup(
     name="tinycdb",
     version="0.1",
     author='Jeethu Rao',
     author_email='jeethu@jeethurao.com',
     cmdclass = {'build_ext': build_ext},
-    ext_modules = [Extension("tinycdb", ["tinycdb.pyx"],
-                             libraries=['cdb'])]
+
+    ext_modules = [
+        Extension(
+            "tinycdb",
+            ["tinycdb.pyx"] + TINYCDB_FILES,
+            include_dirs=[TINYCDB_DIR],
+        )
+    ]
 )
 
 
+cdb
+cdb-shared
+tinycdb-*.tar.gz
+tests.out
+*.lo
+lib*.so*
+build-stamp
+
+Makefile.perl
+CDB_File.bs
+CDB_File.c
+blib
+2006-06-29  Michael Tokarev  <mjt@corpit.ru>
+
+	* see debian/changelog file for further changes.
+
+2005-04-18  Michael Tokarev  <mjt@corpit.ru>
+
+	* move cdb_make_find.c content into cdb_make_put.c
+
+	* introduce CDB_PUT_REPLACE0 - zerofill old duplicates
+
+	* allow usage of cdb.h in C++
+
+2005-04-11  Michael Tokarev  <mjt@corpit.ru>
+
+	* do not autogenerate files (cdb.h.in, cdb.3.in etc), but
+	  use real files instead (only substituted VERSION and NAME)
+
+	* finally fixed the `!fputs()' condition to be `fputs() < 0'
+	  as it should be in cdb.c (misbehaves on *bsd)
+
+	* kill cdbi_t usage in cdb_int.h
+
+	* export _cdb_make_fullwrite() (was ewrite()) and _cdb_make_flush()
+	  and use them in cdb_make.c as appropriate
+
+	* almost completely rewrite _cdb_make_find() and friends:
+	  - _cdb_make_find() now accepts new parameter, remove (bool), which,
+	    if true, indicates all found records should be deleted from the
+	    database.
+	  - Call _cdb_make_find() with remove=0 from cdb_make_exists()
+	  - Call _cdb_make_find() with appropriate arguments from
+	    cdb_make_put(), and simplify the latter greatly (was too clumsy
+	    anyway)
+
+	* rename `flags' parameter in cdb_make_put() to be `mode' which is
+	  more appropriate
+
+	* change #if conditional in nss_cdb.c
+	  from __GLIBC__ to __GNU_LIBRARY__
+
+2003-11-04  Michael Tokarev  <mjt@corpit.ru>
+
+	* added cdb_get() routine: tinycdb officially uses mmap.
+
+	* added cdb_{get,read}{data,key}() macros to read and get
+	  current data and key.
+
+	* fixed bug in cdb_seek() - incorrect wrap, sometimes
+	  cdb_seek()+cdb_bread() may return EIO instead of finding
+	  correct record.
+	
+	* added some tweaks to Makefile to build position-independent
+	  libcdb_pic.a and shared libcdb.so libraries.  Note that
+	  using libcdb as shared library is probably not a good idea,
+	  due to tiny size of the library.
+
+	* added initial nss_cdb module.  Still not well-tested.
+	  Probably will not build on non-GNU system.
+
+	* adjusted tests.{ok,sh} for latest cdb utility modifications
+	  (-a mode in query by default)
+
+	* Victor Porton (porton at ex-code.com) provided a patch
+	  to allow tinycdb to be built on win32 platform (cdb_init.c).
+	  Completely untested.
+
+2003-08-13  Michael Tokarev  <mjt@corpit.ru>
+
+	* s/cdbi_t/unsigned/g.  No need to keep this type.
+
+	* changed usage of cdb_findnext(): one need to pass
+	  pointer to cdb structure to cdb_findnext() now,
+	  and should use cdb_datapos(struct cdb_find *)
+	  instead of cdb_datapos(struct cdb *)
+
+	* added cdb_seqinit() and cdb_seqnext() routines for sequential
+	  record enumeration
+
+	* addded cdb_dend to the cdb structure: end of data
+	  position.  Use that in cdb_seq*().
+
+	* more strict checking: ensure data is within data section,
+	  and hash tables are within hash section of a file.
+
+	* cdb_make.c (cdb_make_start): zerofill cdb_make structure
+	  to shut valgrind up (writing uninitialized data to file)
+	
+	* cdb.c (cmode): always open file in RDWR mode to allow
+	  duplicate key detection
+
+2002-12-08  Michael Tokarev  <mjt+cdb@corpit.ru>
+
+	* version 0.73
+	* de-Debianization.  Oh well... ;)
+	* no code changes, just like in 0.72
+
+2002-10-13  Michael Tokarev  <mjt+cdb@corpit.ru>
+
+	* version 0.72
+
+	* cleaned up debian packaging and made it actually work
+
+	* no code changes
+
+2002-07-22  Michael Tokarev <mjt+cdb@corpit.ru>
+
+	* version 0.71
+
+	* rearranged object files to not depend on ranlib on
+	  systems that requires it (i.e. OpenBSD)
+
+	* use ranlib but mark it's possible error as non-fatal
+
+2001-12-10  Michael Tokarev <mjt+cdb@corpit.ru>
+
+	* version 0.7a
+
+	* converted to CVS, added two missing #include <stdlib.h> for
+	  malloc declaration and spec target to the Makefile
+
+2001-10-14  Michael Tokarev <mjt+cdb@corpit.ru>
+
+	* version 0.7
+
+	* added cdb_seek() and cdb_bread() routines as found
+	  in freecdb/cdb-0.64
+
+2001-07-26  Michael Tokarev <mjt+cdb@corpit.ru>
+
+	* version 0.6
+
+	* added another option, CDB_PUT_WARN, to cdb_make_put's flags
+	  (to allow adding unconditionally but still warn about dups),
+	  now cdb_make_put seems to be logically complete.
+
+	* added and documented -r and -u options for cdb(1) command,
+	  and made them consistent with -w and -e also.
+
+	* reorganized cdb(1) manpage and added changes made to cdb
+	  command.
+
+	* added version references to manpages (and make them autogenerated
+	  to simplify maintenance).
+
+	* added cdb(5) manpage describing CDB file format.
+
+2001-07-25  Michael Tokarev <mjt+cdb@corpit.ru>
+
+	* version 0.5
+
+	* added missing #include <sys/types.h> in cdb_init.c, thanks to
+	  ppetru@ppetru.net (Petru Paler)
+
+	* removed usage of pread() in cdb_make_find() and friends,
+	  suggested by Liviu Daia <Liviu.Daia@imar.ro>
+
+	* autogenerate tinycdb.spec file from template and debian/changelog
+
+	* autogenerate cdb.h from cdb.h.in (substituting version)
+
+2001-06-29  Michael Tokarev <mjt+cdb@corpit.ru>
+
+	* version 0.4
+
+	* added cdb_make_put() routine to conditionnaly add a record
+
+	* split cdb library to more files (finer granularity)
+
+	* added cdb_findinit() and cdb_findnext() routines
+
+	* renamed cdbtool to cdb
+
+	* simplified cdb utility (dropped various format spec, changed
+	  options parsing) and a manpage
+
+	* added small note and copyright to every file in package
+
+	* added some testsuite (make test)
+
+2001-05-27  Michael Tokarev <mjt+cdb@corpit.ru>
+
+	* version 0.3
+
+	* Initial Release.
+#! /usr/bin/make -rf
+# $Id: Makefile,v 1.28 2009-01-31 17:12:21 mjt Exp $
+# make file for tinycdb package
+#
+# This file is a part of tinycdb package by Michael Tokarev, mjt@corpit.ru.
+# Public domain.
+
+VERSION = 0.77
+
+prefix=/usr/local
+exec_prefix=$(prefix)
+bindir=$(exec_prefix)/bin
+libdir=$(exec_prefix)/lib
+syslibdir=$(libdir)
+sysconfdir=/etc
+includedir=$(prefix)/include
+mandir=$(prefix)/man
+NSSCDB_DIR = $(sysconfdir)
+DESTDIR=
+
+CC = cc
+CFLAGS = -O
+
+AR = ar
+ARFLAGS = rv
+RANLIB = ranlib
+
+NSS_CDB = libnss_cdb.so.2
+LIBBASE = libcdb
+LIB = $(LIBBASE).a
+PICLIB = $(LIBBASE)_pic.a
+SHAREDLIB = $(LIBBASE).so.1
+SOLIB = $(LIBBASE).so
+CDB_USELIB = $(LIB)
+NSS_USELIB = $(PICLIB)
+LIBMAP = $(LIBBASE).map
+INSTALLPROG = cdb
+
+# The following assumes GNU CC/LD -
+# used for building shared libraries only
+CFLAGS_PIC = -fPIC
+CFLAGS_SHARED = -shared
+CFLAGS_SONAME = -Wl,--soname=
+CFLAGS_VSCRIPT = -Wl,--version-script=
+
+CP = cp
+
+LIB_SRCS = cdb_init.c cdb_find.c cdb_findnext.c cdb_seq.c cdb_seek.c \
+ cdb_unpack.c \
+ cdb_make_add.c cdb_make_put.c cdb_make.c cdb_hash.c
+NSS_SRCS = nss_cdb.c nss_cdb-passwd.c nss_cdb-group.c nss_cdb-spwd.c
+NSSMAP = nss_cdb.map
+
+DISTFILES = Makefile cdb.h cdb_int.h $(LIB_SRCS) cdb.c \
+ $(NSS_SRCS) nss_cdb.h nss_cdb-Makefile \
+ cdb.3 cdb.1 cdb.5 \
+ tinycdb.spec tests.sh tests.ok \
+ $(LIBMAP) $(NSSMAP) \
+ ChangeLog NEWS
+DEBIANFILES = debian/control debian/rules debian/copyright debian/changelog
+
+all: static
+static: staticlib cdb
+staticlib: $(LIB)
+nss: $(NSS_CDB)
+piclib: $(PICLIB)
+sharedlib: $(SHAREDLIB)
+shared: sharedlib cdb-shared
+
+LIB_OBJS = $(LIB_SRCS:.c=.o)
+LIB_OBJS_PIC = $(LIB_SRCS:.c=.lo)
+NSS_OBJS = $(NSS_SRCS:.c=.lo)
+
+$(LIB): $(LIB_OBJS)
+	-rm -f $@
+	$(AR) $(ARFLAGS) $@ $(LIB_OBJS)
+	-$(RANLIB) $@
+
+$(PICLIB): $(LIB_OBJS_PIC)
+	-rm -f $@
+	$(AR) $(ARFLAGS) $@ $(LIB_OBJS_PIC)
+	-$(RANLIB) $@
+
+$(SHAREDLIB): $(LIB_OBJS_PIC) $(LIBMAP)
+	-rm -f $(SOLIB)
+	ln -s $@ $(SOLIB)
+	$(CC) $(CFLAGS) $(CFLAGS_SHARED) -o $@ \
+	 $(CFLAGS_SONAME)$(SHAREDLIB) $(CFLAGS_VSCRIPT)$(LIBMAP) \
+	 $(LIB_OBJS_PIC)
+
+cdb: cdb.o $(CDB_USELIB)
+	$(CC) $(CFLAGS) -o $@ cdb.o $(CDB_USELIB)
+cdb-shared: cdb.o $(SHAREDLIB)
+	$(CC) $(CFLAGS) -o $@ cdb.o $(SHAREDLIB)
+
+$(NSS_CDB): $(NSS_OBJS) $(NSS_USELIB) $(NSSMAP)
+	$(CC) $(CFLAGS) $(CFLAGS_SHARED) -o $@ \
+	 $(CFLAGS_SONAME)$@ $(CFLAGS_VSCRIPT)$(NSSMAP) \
+	 $(NSS_OBJS) $(NSS_USELIB)
+
+.SUFFIXES:
+.SUFFIXES: .c .o .lo
+
+.c.o:
+	$(CC) $(CFLAGS) -c $<
+.c.lo:
+	$(CC) $(CFLAGS) $(CFLAGS_PIC) -c -o $@ -DNSSCDB_DIR=\"$(NSSCDB_DIR)\" $<
+
+cdb.o: cdb.h
+$(LIB_OBJS) $(LIB_OBJS_PIC): cdb_int.h cdb.h
+$(NSS_OBJS): nss_cdb.h cdb.h
+
+clean:
+	-rm -f *.o *.lo core *~ tests.out tests-shared.ok
+realclean distclean:
+	-rm -f *.o *.lo core *~ $(LIBBASE)[._][aps]* $(NSS_CDB)* cdb cdb-shared
+
+test tests check: cdb
+	sh ./tests.sh ./cdb > tests.out 2>&1
+	diff tests.ok tests.out
+	@echo All tests passed
+test-shared tests-shared check-shared: cdb-shared
+	sed 's/^cdb: /cdb-shared: /' <tests.ok >tests-shared.ok
+	LD_LIBRARY_PATH=. sh ./tests.sh ./cdb-shared > tests.out 2>&1
+	diff tests-shared.ok tests.out
+	rm -f tests-shared.ok
+	@echo All tests passed
+
+do_install = \
+ while [ "$$1" ] ; do \
+   if [ .$$4 = .- ]; then f=$$1; else f=$$4; fi; \
+   d=$(DESTDIR)$$3 ; echo installing $$1 to $$d/$$f; \
+   [ -d $$d ] || mkdir -p $$d || exit 1 ; \
+   $(CP) $$1 $$d/$$f || exit 1; \
+   chmod 0$$2 $$d/$$f || exit 1; \
+   shift 4; \
+ done
+
+install-all: all $(INSTALLPROG)
+	set -- \
+	 cdb.h 644 $(includedir) - \
+	 cdb.3 644 $(mandir)/man3 - \
+	 cdb.1 644 $(mandir)/man1 - \
+	 cdb.5 644 $(mandir)/man5 - \
+	 $(INSTALLPROG) 755 $(bindir) cdb \
+	 libcdb.a 644 $(libdir) - \
+	 ; \
+	$(do_install)
+install-nss: nss
+	@set -- $(NSS_CDB) 644 $(syslibdir) - \
+	        nss_cdb-Makefile 644 $(sysconfdir) cdb-Makefile ; \
+	$(do_install)
+install-sharedlib: sharedlib
+	@set -- $(SHAREDLIB) 644 $(libdir) - ; \
+	$(do_install) ; \
+	ln -sf $(SHAREDLIB) $(DESTDIR)$(libdir)/$(LIBBASE).so
+install-piclib: piclib
+	@set -- $(PICLIB) 644 $(libdir) - ; \
+	$(do_install)
+install: install-all
+
+DNAME = tinycdb-$(VERSION)
+dist: $(DNAME).tar.gz
+$(DNAME).tar.gz: $(DISTFILES) $(DEBIANFILES)
+	mkdir $(DNAME) $(DNAME)/debian
+	ln $(DISTFILES) $(DNAME)/
+	ln $(DEBIANFILES) $(DNAME)/debian/
+	tar cfz $@ $(DNAME)
+	rm -fr $(DNAME)
+
+.PHONY: all clean realclean dist spec
+.PHONY: test tests check test-shared tests-shared check-shared
+.PHONY: static staticlib shared sharedlib nss piclib
+.PHONY: install install-all install-sharedlib install-piclib install-nss
+$Id: NEWS,v 1.6 2009-01-31 17:12:21 mjt Exp $
+User-visible news.  Latest at the top.
+
+tinycdb-0.77
+
+ - bugfix release: manpage typos, portability fixes and the like
+
+ - bugfix: improper logic in EINTR handling in _cdb_make_full_write
+   routine which may lead to corruped .cdb file.
+
+tinycdb-0.76
+
+ - new cdb utility option: -p permissions, to specify permission
+   bits for the newly created database file.
+
+ - cdb utility, when creating the database, does unlink() of the
+   (temp) db file and when opens it with O_CREAT|O_EXCL, instead
+   of previous O_CREAT|O_TRUNC w/o unlink() behaviour, and uses
+   O_NOFOLLOW flag if available.  This avoids any possible symlink
+   race conditions, but is somewhat incompatible with previous
+   versions, where it was possible to create temp file beforehand
+   with proper permissions.  Together with new -p option (above),
+   this isn't that big change.
+
+ - cdb utility now allows to omit temp file usage (with final rename())
+   when creating the database, by specifying -t- (`-' as temp file name)
+   (or -t the_same_name_as_the_db_file).  Useful if the utility is called
+   from a script which does its own rename afterwards.
+
+ - alot of minor code changes to make modern compilers happy (mostly
+   signed char vs unsigned char "fixes").  Including additions of
+   `unsigned' into common structure definitions in cdb.h - the API
+   stays compatible still.
+
+ - several (spelling and other) fixes for manpages.
+
+ - tinycdb is, once again, maintained as a native Debian package.
+   Debian package has been split into 4 parts: libcdb1, libcdb-dev,
+   tinycdb (the utility) and libnss_cdb.  RPM .spec file now builds
+   two packages as well: tinycdb (includes shared library, the utility,
+   and nss_cdb module) and tinycdb-devel (development files).
+
+tinycdb-0.75 (2005-04-11)
+
+ - make cdb_make_put(CDB_PUT_REPLACE) to actually *remove*
+   all (was only first previously) matching records, by
+   rewriting the file.
+
+ - new mode CDB_PUT_REPLACE0, which zeroes out old duplicate
+   records
+
+ - fixed fputs() == NULL condition in cdb.c, finally
+   (should be < 0, not == NULL)
+
+tinycdb-0.74 (2003-11-04)
+
+ - reworked cdb utility, see manpage for details.  cdb -q now
+   prints all matching records by default (incompat change),
+   use cdb -q -n XX to return only XXth record if any.
+   -m works with -q.
+
+ - there are several new routines (and macros) in library.
+   - cdb_seqinit() and cdb_seqstart() to fetch all records
+   - cdb_get() to get a pointer to data in internal buffer
+   - cdb_{get,read}{data,key}()
+
+ - cdbi_t gone (but still defined).  Use unsigned instead.
+
+ - cdb_findnext() changed
+
+ - fixed a bug in cdb_seek() (EIO instead of getting valid record)
+
+ - added nss_cdb (for passwd, group and shadow databases only)
+
+ - Makefile targets to build PIC code (libcdb_pic.a, required for
+   nss_cdb) and shared library (probably not a good idea)
+
+ - Modifications to allow tinycdb to compile under win32,
+   by Victor Porton (porton at ex-code.com).

src/cdb

Binary file added.
+.\" $Id: cdb.1,v 1.12 2009-01-31 17:12:22 mjt Exp $
+.\" cdb command tool manpage
+.\"
+.\" This file is a part of tinycdb package by Michael Tokarev, mjt@corpit.ru.
+.\" Public domain.
+.\"
+.TH cdb 1 "Jan 2009"
+.SH NAME
+cdb \- Constant DataBase manipulation tool
+.SH SYNOPSYS
+\fBcdb\fR \-q [\-m] [\-n \fInum\fR] \fIdbname\fR \fIkey\fR
+.br
+\fBcdb\fR \-d [\-m] [\fIdbname\fR|\-]
+.br
+\fBcdb\fR \-l [\-m] [\fIdbname\fR|\-]
+.br
+\fBcdb\fR \-s [\fIdbname\fR|\-]
+.br
+\fBcdb\fR \-c [\-m] [\-t \fItmpname\fR|\-] [\-p \fIperms\fR] [\-weru0] \fIdbname\fR [\fIinfile\fR...]
+
+.SH DESCRIPTION
+
+\fBcdb\fR used to query, dump, list, analyze or create CDB (Constant
+DataBase) files.  Format of cdb described in \fIcdb\fR(5) manpage.
+This manual page corresponds to version \fB0.77\fR of \fBtinycdb\fR
+package.
+
+.SS Query
+
+\fBcdb \-q\fR finds given \fIkey\fR in a given \fIdbname\fR
+cdb file, and writes associated value to standard output if found (and
+exits with zero), or exits with non\-zero if not found.  \fIdbname\fR must
+be seekable file, and stdin can not be used as input.
+By default, \fBcdb\fR will print \fIall\fR records found.
+Options recognized in query mode:
+
+.IP \fB\-n\fInum\fR
+causes \fBcdb\fR to find and write a record with a given number \fInum\fR
+starting with 1 \(em when there are many records with a given key.
+
+.IP \fB\-m\fR
+newline will be added after every value printed.  By default, multiple
+values will be written without any delimiter.
+
+.SS "Dump/List"
+
+\fBcdb \-d\fR dumps contents, and \fBcdb \-l\fR lists keys
+of \fIcdbfile\fR (or standard input if not specified) to standard
+output, in format controlled by presence of \fB\-m\fR option.
+See subsection "Formats" below.  Output from \fBcdb \-d\fR
+can be used as an input for \fBcdb \-c\fR.
+
+.SS Create
+
+Cdb database created in two stages: temporary database is created,
+and after it is complete, it gets atomically renamed to permanent
+place.  This avoids requirements for locking between readers and
+writers (or creaters).  \fBcdb \-c\fR will attempt to create
+cdb in file \fItmpname\fR (or \fIdbname\fR with ".tmp" appended
+if no \-t option given) and then rename it to \fIdbname\fR.  It
+will read supplied \fIinfile\fRs (or standard input if none specified).
+Options recognized in create mode:
+
+.IP "\fB\-t \fItmpname\fR"
+use given \fItmpname\fR as temporary file.  Defaults to
+\fIdbname\fR.tmp (i.e. with output file with .tmp added).
+Note \fItmpname\fR must be in the same filesystem as output file, as
+.B cdb
+uses
+.IR rename (2)
+to finalize the database creation procedure.
+If \fItmpname\fR is a single dash (\-), no temp file will be created,
+database will be built in\-place.  This mode is useful when the final
+renaming is done by the caller.
+
+.IP "\fB\-p \fIperms\fR"
+permissions for the newly created file (usually an octal number, like 0644).
+By default the permissions are 0666 (with current process umask applied).
+If this option is specified, current umask value has no effect.
+
+.IP \fB\-w\fR
+warn about duplicate keys.
+
+.IP \fB\-e\fR
+abort on duplicate keys (implies \-w).
+
+.IP \fB\-r\fR
+replace existing key with new one in case of duplicate.
+This may require database file rewrite to remove old
+records, and can be slow.
+
+.IP \fB\-0\fR
+zero-fill existing records when duplicate records are
+added.  This is faster than \fB\-r\fR, but leaves extra
+zeros in the database file in case of duplicates.
+
+.IP \fB\-u\fR
+do not add duplicate records.
+
+.IP \fB\-m\fR
+interpret input as a sequence of lines, one record per line,
+with value separated from a key by space or tab characters,
+instead of native cdb format (see "Input/Output Format" below).
+
+.PP
+Note that using any option that requires duplicate checking will
+slow creation process \fIsignificantly\fR, especially for large
+databases.
+
+.SS Statistics
+
+\fBcdb \-s\fR will analyze \fIdbfile\fR and print summary to
+standard output.  Statistics include: total number of rows in
+a file, minimum, average and maximum key and value lengths,
+hash tables (max 256) and entries used, number of hash collisions
+(that is, more than one key point to the same hash table entry),
+minimum, average and maximum hash table size (of non-empty tables),
+and number of keys that sits at 10 different distances from
+it's calculated hash table index \(em keys in distance 0 requires
+only one hash table lookup, 1 \(em two and so on; more keys at
+greater distance means slower database search.
+
+.SS "Input/Output Format"
+
+By default, \fBcdb\fR expects (for create operation) or writes
+(for dump/list) native cdb format data.  Cdb native format is
+a sequence of records in a form:
+.br
+    +\fIklen\fR,\fIvlen\fR:\fIkey\fR\->\fIval\fR\\n
+.br
+where "+", ",", ":", "\-", ">" and "\\n" (newline) are literal characters,
+\fIklen\fR and \fIvlen\fR are length of key and value as decimal numbers,
+and \fIkey\fR and \fIval\fR are key and value themselves.  Series of
+records terminated by an empty line.  This is the only format where
+key and value may contain any character including newline, zero (\\0)
+and so on.
+
+When \fB\-l\fR option requested (list keys mode), \fBcdb\fR will produce
+slightly modified output in a form:
+.br
+    +\fIklen\fR:\fIkey\fR\\n
+.br
+(note \fIvlen\fR and \fIval\fR are omitted, together with surrounding
+delimiters).
+
+If \fB\-m\fR option is given, \fBcdb\fR will expect or produce one line
+for every record (newline is a record delimiter), and every line should
+contain optional whitespace, key, whitespace and value up to end of line.
+Lines started with hash character (#) and empty lines are ignored.
+This is the same format as \fBmkmap\fR(1) utility expects.
+
+.SH "OPTIONS SUMMARY"
+
+Here is a short summary of all options accepted by \fBcdb\fR utility:
+
+.IP \fB\-0\fR
+zero-fill duplicate records in create (\fB\-c\fR) mode.
+.IP \fB\-c\fR
+create mode.
+.IP \fB\-d\fR
+dump mode.
+.IP \fB\-e\fR
+abort (error) on duplicate key in create (\fB\-c\fR) mode.
+.IP \fB\-h\fR
+print short help and exit.
+.IP \fB\-l\fR
+list mode.
+.IP \fB\-m\fR
+input or output is in "map" format, not in native cdb format.  In query
+mode, add a newline after every value written.
+.IP \fB\-n\fInum\fR
+find and print \fInum\fRth record in query (\fB\-q\fR) mode.
+.IP \fB\-q\fR
+query mode.
+.IP \fB\-r\fR
+replace duplicate keys in create (\fB\-c\fR) mode.
+.IP \fB\-s\fR
+statistics mode.
+.IP "\fB\-t\fR \fItempfile\fR"
+specify temporary file when creating (\fB\-c\fR) cdb file (use single dash
+(\-) as \fItempfile\fR to stop using temp file).
+.IP \fB\-u\fR
+do not insert duplicate keys (unique) in create (\fB\-c\fR) mode.
+.IP \fB\-w\fR
+warn about duplicate keys in create (\fB\-c\fR) mode.
+
+.SH AUTHOR
+
+The \fBtinycdb\fR package written by Michael Tokarev <mjt@corpit.ru>,
+based on ideas and shares file format with original cdb library by
+Dan Bernstein.
+
+.SH "SEE ALSO"
+cdb(5), cdb(3).
+
+.SH LICENCE
+Public domain.
+
+.\" $Id: cdb.3,v 1.11 2009-01-31 17:12:22 mjt Exp $
+.\" cdb library manpage
+.\"
+.\" This file is a part of tinycdb package by Michael Tokarev, mjt@corpit.ru.
+.\" Public domain.
+.\"
+.TH cdb 3 "Jun 2006"
+
+.SH NAME
+cdb \- Constant DataBase library
+
+.SH SYNOPSYS
+
+.nf
+.ft B
+ #include <cdb.h>
+ cc ... \-lcdb
+.ft R
+.fi
+
+.SH DESCRIPTION
+
+.B cdb
+is a library to create and access Constant DataBase files.
+File stores (key,value) pairs and used to quickly find a
+value based on a given key.  Cdb files are create-once files,
+that is, once created, file cannot be updated but recreated
+from scratch -- this is why database is called \fIconstant\fR.
+Cdb file is optimized for quick access.  Format of such file
+described in \fIcdb\fR(5) manpage.  This manual page corresponds
+to version \fB0.77\fR of \fBtinycdb\fR package.
+
+Library defines two non-interlaced interfaces: for querying
+existing cdb file data (read-only mode) and for creating
+such a file (almost write-only).  Strictly speaking, those
+modes allows very limited set of opposite operation as well
+(i.e. in query mode, it is possible to update key's value).
+
+All routines in this library are thread-safe as no global
+data used, except of \fIerrno\fR variable for error indication.
+
+.B cdb
+datafiles may be moved between systems safely, since format
+does not depend on architecture.
+
+.SH "QUERY MODE"
+
+There are two query modes available.  First uses a structure
+that represents a cdb database, just like \fBFILE\fR structure
+in stdio library, and another works with plain filedescriptor.
+First mode is more sophisticated and flexible, and usually somewhat
+faster.  It uses \fBmmap\fR(2) internally.  This mode may look
+more "natural" or object-oriented compared to second one.
+
+The following routines works with any mode:
+
+.nf
+unsigned \fBcdb_unpack\fR(\fIbuf\fR)
+   const unsigned char \fIbuf\fR[4];
+.fi
+.RS
+helper routine to convert 32-bit integer from internal representation
+to machine format.  May be used to handle application integers in
+a portable way.  There is no error return.
+.RE
+
+.SS "Query Mode 1"
+
+All query operations in first more deals with common data
+structure, \fBstruct cdb\fR, associated with an open file
+descriptor.  This structure is opaque to application.
+
+The following routines exists for accessing \fBcdb\fR
+database:
+
+.nf
+int \fBcdb_init\fR(\fIcdbp\fR, \fIfd\fR)
+   struct cdb *\fIcdbp\fR;
+   int \fIfd\fR;
+.fi
+.RS
+initializes structure given by \fIcdbp\fR pointer and associates
+it with opened file descriptor \fIfd\fR.  Memory allocation for
+structure itself if needed and file open operation should be done
+by application.  File \fIfd\fR should be opened at least read-only,
+and should be seekable.  Routine returns 0 on success or negative
+value on error.
+.RE
+
+.nf
+void \fBcdb_free\fR(\fIcdbp\fR)
+   struct cdb *\fIcdbp\fR;
+.fi
+.RS
+frees internal resources held by structure.  Note that this
+routine does \fInot\fR closes a file.
+.RE
+
+.nf
+int \fBcdb_fileno\fR(\fIcdbp\fR)
+  const struct cdb *\fIcdbp\fR;
+.fi
+.RS
+returns filedescriptor associated with cdb (as was passed to
+\fBcdb_init\fR()).
+.RE
+
+.nf
+int \fBcdb_read\fR(\fIcdbp\fR, \fIbuf\fR, \fIlen\fR, \fIpos\fR)
+int \fBcdb_readdata\fR(\fIcdbp\fR, \fIbuf\fR, \fIlen\fR, \fIpos\fR)
+int \fBcdb_readkey\fR(\fIcdbp\fR, \fIbuf\fR, \fIlen\fR, \fIpos\fR)
+   const struct cdb *\fIcdbp\fR;
+   void *\fIbuf\fR;
+   unsigned \fIlen\fR;
+   unsigned \fIpos\fR;
+.fi
+.RS
+reads a data from cdb file, starting at position \fIpos\fR of length
+\fIlen\fR, placing result to \fIbuf\fR.  This routine may be used
+to get actual value found by \fBcdb_find\fR() or other routines
+that returns position and length of a data.  Returns 0 on success
+or negative value on error.
+Routines \fBcdb_readdata\fR() and \fBcdb_readkey\fR() are shorthands
+to read current (after e.g. \fBcdb_find\fR()) data and key
+respectively, using \fBcdb_read\fR().
+.RE
+
+.nf
+const void *\fBcdb_get\fR(\fIcdbp\fR, \fIlen\fR, \fIpos\fR)
+const void *\fBcdb_getdata\fR(\fIcdbp\fR)
+const void *\fBcdb_getkey\fR(\fIcdbp\fR)
+   const struct cdb *\fIcdbp\fR;
+   unsigned \fIlen\fR;
+   unsigned \fIpos\fR;
+.fi
+.RS
+Internally, cdb library uses memory-mmaped region to access the on-disk
+database.  \fBcdb_get\fR() allows to access internal memory in a way
+similar to \fBcdb_read\fR() but without extra copying and buffer
+allocation.  Returns pointer to actual data on success or NULL on
+error (position points to outside of the database).
+Routines \fBcdb_getdata\fR() and \fBcdb_getkey\fR() are shorthands
+to access current (after e.g. \fBcdb_find\fR()) data and key
+respectively, using \fBcdb_get\fR().
+.RE
+
+.nf
+int \fBcdb_find\fR(\fIcdbp\fR, \fIkey\fR, \fIklen\fR)
+unsigned \fBcdb_datapos\fR(\fIcdbp\fR)
+unsigned \fBcdb_datalen\fR(\fIcdbp\fR)
+unsigned \fBcdb_keypos\fR(\fIcdbp\fR)
+unsigned \fBcdb_keylen\fR(\fIcdbp\fR)
+   struct cdb *\fIcdbp\fR;
+   const void *\fIkey\fR;
+   unsigned \fIklen\fR;
+.fi
+.RS
+attempts to find a key given by (\fIkey\fR,\fIklen\fR) parameters.
+If key exists in database, routine returns 1 and places position
+and length of value associated with this key to internal fields
+inside \fIcdbp\fR structure, to be accessible by \fBcdb_datapos\fR(\fIcdbp\fR)
+and \fBcdb_datalen\fR(\fIcdbp\fR) routines.  If key is not in database,
+\fBcdb_find\fR() returns 0.  On error, negative value is returned.
+Data pointers (available via \fBcdb_datapos\fR() and \fBcdb_datalen\fR())
+gets updated only in case of successful search.  Note that using
+\fBcdb_find\fR() it is possible to lookup only \fIfirst\fR record
+with a given key.
+.RE
+
+.nf
+int \fBcdb_findinit(\fIcdbfp\fR, \fIcdbp\fR, \fIkey\fR, \fIklen\fR)
+int \fBcdb_findnext\fR(\fIcdbfp\fR)
+  struct cdb_find *\fIcdbfp\fR;
+  const struct cdb *\fIcdbp\fR;
+  const void *\fIkey\fR;
+  unsigned \fIklen\fR;
+.fi
+.RS
+sequential-find routines that used separate structure.  It is
+possible to have more than one record with the same key in a
+database, and these routines allows to enumerate all them.
+\fBcdb_findinit\fR() initializes search structure pointed to
+by \fIcdbfp\fR.  It will return negative value on error or
+non-negative value on success.  \fBcdb_findnext\fR() attempts
+to find next (first when called right after \fBcdb_findinit\fR())
+matching key, setting value position and length in \fIcdbfp\fR
+structure.  It will return positive value if given key was
+found, 0 if there is no more such key(s), or negative value
+on error.  To access value position and length after successful
+call to \fBcdb_findnext\fR() (when it returned positive result),
+use \fBcdb_datapos\fR(\fIcdbp\fR) and \fBcdb_datalen\fR(\fIcdbp\fR)
+routines.  It is error to continue using \fBcdb_findnext\fR() after
+it returned 0 or error condition (\fBcdb_findinit\fR() should be
+called again).  Current data pointers (available via \fBcdb_datapos\fR()
+and \fBcdb_datalen\fR()) gets updated only on successful search.
+.RE
+
+.nf
+void \fBcdb_seqinit\fR(\fIcptr\fR, \fIcdbp\fR)
+int \fBcdb_seqnext\fR(\fIcptr\fR, \fIcdbp\fR)
+  unsigned *\fIcptr\fR;
+  struct cdb *\fIcdbp\fR;
+.fi
+.RS
+sequential enumeration of all records stored in cdb file.
+\fBcdb_seqinit\fR() initializes access current data pointer \fIcptr\fR
+to point before first record in a cdb file. \fBcdb_seqnext\fR() updates
+data pointers in \fIcdbp\fR to point to the next record and updates
+\fIcptr\fR, returning positive value on success, 0 on end of data condition
+and negative value on error.  Current record will be available after
+successful operation using \fBcdb_datapos\fR(\fIcdbp\fR) and
+\fBcdb_datalen\fR(\fIcdbp\fR) (for the data) and \fBcdb_keypos\fR(\fIcdbp\fR)
+and \fBcdb_keylen\fR(\fIcdbp\fR) (for the key of the record).
+Data pointers gets updated only in case of successful operation.
+.RE
+
+.SS "Query Mode 2"
+
+In this mode, one need to open a \fBcdb\fR file using one of
+standard system calls (such as \fBopen\fR(2)) to obtain a
+filedescriptor, and then pass that filedescriptor to cdb routines.
+Available methods to query a cdb database using only a filedescriptor
+include:
+
+.nf
+int \fBcdb_seek\fR(\fIfd\fR, \fIkey\fR, \fIklen\fR, \fIdlenp\fR)
+  int \fIfd\fR;
+  const void *\fIkey\fR;
+  unsigned \fIklen\fR;
+  unsigned *\fIdlenp\fR;
+.fi
+.RS
+searches a cdb database (as pointed to by \fIfd\fR filedescriptor)
+for a key given by (\fIkey\fR, \fIklen\fR), and positions file pointer
+to start of data associated with that key if found, so that next read
+operation from this filedescriptor will read that value, and places
+length of value, in bytes, to variable pointed to by \fIdlenp\fR.
+Returns positive value if operation was successful, 0 if key was not
+found, or negative value on error.  To read the data from a cdb file,
+\fBcdb_bread\fR() routine below can be used.
+.RE
+
+.nf
+int \fBcdb_bread\fR(\fIfd\fR, \fIbuf\fR, \fIlen\fR)
+  int \fIfd\fR;
+  void *\fIbuf\fR;
+  int \fIlen\fR;
+.fi
+.RS
+reads data from a file (as pointed to by \fIfd\fR filedescriptor) and
+places \fIlen\fR bytes from this file to a buffer pointed to by \fIbuf\fR.
+Returns 0 if exactly \fIlen\fR bytes was read, or a negative value in
+case of error or end-of-file.  This routine ignores interrupt errors (EINTR).
+Sets errno variable to \fBEIO\fR in case of end-of-file condition (when
+there is less than \fIlen\fR bytes available to read).
+.RE
+
+.SS Notes
+
+Note that \fIvalue\fR of any given key may be updated in place
+by another value of the same size, by writing to file at position
+found by \fBcdb_find\fR() or \fBcdb_seek\fR().  However one should
+be very careful when doing so, since write operation may not succeed
+in case of e.g. power failure, thus leaving corrupted data.  When
+database is (re)created, one can guarantee that no incorrect data
+will be written to database, but not with inplace update.  Note
+also that it is not possible to update any key or to change length
+of value.
+
+.SS
+
+.SH "CREATING MODE"
+
+.B cdb
+database file should usually be created in two steps: first, temporary
+file created and written to disk, and second, that temporary file
+is renamed to permanent place.  Unix rename(2) call is atomic operation,
+it removes destination file if any AND renaes another file in one
+step.  This way it is guaranteed that readers will not see incomplete
+database.  To prevent multiple simultaneous updates, locking may
+also be used.
+
+All routines used to create \fBcdb\fR database works with
+\fBstruct cdb_make\fR object that is opaque to application.
+Application may assume that \fBstruct cdb_make\fR has at least
+the same member(s) as published in \fBstruct cdb\fR above.
+
+.nf
+int \fBcdb_make_start\fR(\fIcdbmp\fR, \fIfd\fR)
+   struct cdb_make *\fIcdbmp\fR;
+   int \fIfd\fR;
+.fi
+.RS
+initializes structure to create a database.  File \fIfd\fR should be
+opened read-write and should be seekable.  Returns 0 on success
+or negative value on error.
+.RE
+
+.nf
+int \fBcdb_make_add\fR(\fIcdbmp\fR, \fIkey\fR, \fIklen\fR, \fIval\fR, \fIvlen\fR)
+   struct cdb_make *\fIcdbmp\fR;
+   const void *\fIkey\fR, *\fIval\fR;
+   unsigned \fIklen\fR, \fIvlen\fR;
+.fi
+.RS
+adds record with key (\fIkey\fR,\fIklen\fR) and value (\fIval\fR,\fIvlen\fR)
+to a database.  Returns 0 on success or negative value on error.  Note that
+this routine does not checks if given key already exists, but \fBcdb_find\fR()
+will not see second record with the same key.  It is not possible to continue
+building a database if \fBcdb_make_add\fR() returned error indicator.
+.RE
+
+.nf
+int \fBcdb_make_finish\fR(\fIcdbmp\fR)
+   struct cdb_make *\fIcdbmp\fR;
+.fi
+.RS
+finalizes database file, constructing all needed indexes, and frees
+memory structures.  It does \fInot\fR closes filedescriptor.
+Returns 0 on success or negative value on error.
+.RE
+
+.nf
+int \fBcdb_make_exists\fR(\fIcdbmp\fR, \fIkey\fR, \fIklen\fR)
+   struct cdb_make *\fIcdbmp\fR;
+   const void *\fIkey\fR;
+   unsigned \fIklen\fR;
+.fi
+.RS
+This routine attempts to find given by (\fIkey\fR,\fIklen\fR) key in
+a not-yet-complete database.  It may significantly slow down the
+whole process, and currently it flushes internal buffer to disk on
+every call with key those hash value already exists in db.  Returns
+0 if such key doesn't exists, 1 if it is, or negative value on error.
+Note that database file should be opened read-write (not write-only)
+to use this routine.  If \fBcdb_make_exists\fR() returned error, it
+may be not possible to continue constructing database.
+.RE
+
+.nf
+int \fBcdb_make_find\fR(\fIcdbmp\fR, \fIkey\fR, \fIklen\fR, \fImode\fR)
+   struct cdb_make *\fIcdbmp\fR;
+   const void *\fIkey\fR;
+   unsigned \fIklen\fR;
+   int \fImode\fR;
+.fi
+.RS
+This routine attempts to find given by (\fIkey\fR,\fIklen\fR) key in
+the database being created.  If the given key is already exists, it
+an action specified by \fImode\fR will be performed:
+.IP \fBCDB_FIND\fR
+checks whenever the given record is already in the database.
+.IP \fBCDB_FIND_REMOVE\fR
+removes all matching records by re-writing the database file accordingly.
+.IP \fBCDB_FIND_FILL0\fR
+fills all matching records with zeros and removes them from index so that
+the records in question will not be findable with \fBcdb_find\fR().  This
+is faster than CDB_FIND_REMOVE, but leaves zero "gaps" in the database.
+Lastly inserted records, if matched, are always removed.
+.PP
+If no matching keys was found, routine returns 0.  In case at least one
+record has been found/removed, positive value will be returned.  On
+error, negative value will be returned and \fBerrno\fR will be set
+appropriately.  When \fBcdb_make_find\fR() returned negative value in
+case of error, it is not possible to continue constructing the database.
+.PP
+\fBcdb_make_exists\fR() is the same as calling \fBcdb_make_find\fR() with
+\fImode\fR set to CDB_FIND.
+.RE
+
+.nf
+int \fBcdb_make_put\fR(\fIcdbmp\fR, \fIkey\fR, \fIklen\fR, \fIval\fR, \fIvlen\fR, \fImode\fR)
+   struct cdb_make *\fIcdbmp\fR;
+   const void *\fIkey\fR, *\fIval\fR;
+   unsigned \fIklen\fR, \fIvlen\fR;
+   int \fImode\fR;
+.fi
+.RS
+This is a somewhat combined \fBcdb_make_exists\fR() and
+\fBcdb_make_add\fR() routines.  \fImode\fR argument controls how
+repeated (already existing) keys will be treated:
+.IP \fBCDB_PUT_ADD\fR
+no duplicate checking will be performed.  This mode is the same as
+\fBcdb_make_add\fR() routine does.
+.IP \fBCDB_PUT_REPLACE\fR
+If the key already exists, it will be removed from the database
+before adding new key,value pair.  This requires moving data in
+the file, and can be quite slow if the file is large.
+All matching old records will be removed this way.  This is the
+same as calling \fBcdb_make_find\fR() with CDB_FIND_REMOVE
+\fImode\fR argument followed by calling \fBcdb_make_add\fR().
+.IP \fBCDB_PUT_REPLACE0\fR
+If the key already exists and it isn't the last record in the file,
+old record will be zeroed out before adding new key,value pair.
+This is alot faster than CDB_PUT_REPLACE, but some extra data will
+still be present in the file.  The data -- old record -- will not
+be accessible by normal searches, but will appear in sequential
+database traversal.  This is the same as calling \fBcdb_make_find\fR()
+with CDB_FIND_FILL0 \fImode\fR argument followed by \fBcdb_make_add\fR().
+.IP \fBCDB_PUT_INSERT\fR
+add key,value pair only if such key does not exists in a database.
+Note that since query (see query mode above) will find first added
+record, this mode is somewhat useless (but allows to reduce database
+size in case of repeated keys).  This is the same as calling
+\fBcdb_make_exists\fR(), followed by \fBcdb_make_add\fR() if
+the key was not found.
+.IP \fBCDB_PUT_WARN\fR
+add key,value pair unconditionally, but also check if this key
+already exists.  This is equivalent of \fBcdb_make_exists\fR()
+to check existence of the given key, unconditionally followed
+by \fBcdb_make_add\fR().
+.PP
+If any error occurred during operations, the routine will return
+negative integer and will set global variable \fBerrno\fR to
+indicate reason of failure.  In case of successful operation
+and no duplicates found, routine will return 0.  If any duplicates
+has been found or removed (which, in case of CDB_PUT_INSERT mode,
+indicates that the new record was not added), routine will return
+positive value.  If an error occurred and \fBcdb_make_put\fR() returned
+negative error, it is not possible to continue database construction
+process.
+.PP
+As with \fBcdb_make_exists\fR() and \fBcdb_make_find\fR(), usage
+of this routine with any but CDB_PUT_ADD mode can significantly
+slow down database creation process, especially when \fImode\fR
+is equal to CDB_PUT_REPLACE0.
+
+.RE
+.nf
+void \fBcdb_pack\fR(\fInum\fR, \fIbuf\fR)
+   unsigned \fInum\fR;
+   unsigned char \fIbuf\fR[4];
+.fi
+.RS
+helper routine that used internally to convert machine integer \fIn\fR
+to internal form to be stored in datafile.  32-bit integer is stored in
+4 bytes in network byte order.  May be used to handle application data.
+There is no error return.
+.RE
+
+.nf
+unsigned \fBcdb_hash\fR(\fIbuf\fR, \fIlen\fR)
+   const void *\fIbuf\fR;
+   unsigned \fIlen\fR;
+.fi
+.RS
+helper routine that calculates cdb hash value of given bytes.
+CDB hash function is
+.br
+  hash[n] = (hash[n\-1] + (hash[n\-1] << 5)) ^ buf[n]
+.br
+starting with
+.br
+  hash[\-1] = 5381
+.br
+.RE
+
+.SH ERRORS
+
+.B cdb
+library may set \fBerrno\fR to following on error:
+
+.IP EPROTO
+database file is corrupted in some way
+.IP EINVAL
+the same as EPROTO above if system lacks EPROTO constant
+.IP EINVAL
+\fIflag\fR argument for \fBcdb_make_put\fR() is invalid
+.IP EEXIST
+\fIflag\fR argument for \fBcdb_make_put\fR() is CDB_PUT_INSERT,
+and key already exists
+.IP ENOMEM
+not enough memory to complete operation (\fBcdb_make_finish\fR and
+\fBcdb_make_add\fR)
+.IP EIO
+set by \fBcdb_bread\fR and \fBcdb_seek\fR if a cdb file is shorter
+than expected or corrupted in some other way.
+
+.SH EXAMPLES
+
+.PP
+Note: in all examples below, error checking is not shown for brewity.
+
+.SS "Query Mode"
+
+.nf
+ int fd;
+ struct cdb cdb;
+ char *key, *data;
+ unsigned keylen, datalen;
+
+ /* opening the database */
+ fd = open(filename, O_RDONLY);
+ cdb_init(&cdb, fd);
+ /* initialize key and keylen here */
+
+ /* single\-record search. */
+ if (cdb_find(&cdb, key, keylen) > 0) {
+   datalen = cdb_datalen(&cdb);
+   data = malloc(datalen + 1);
+   cdb_read(&cdb, data, datalen, cdb_datapos(&cdb));
+   data[datalen] = '\\0';
+   printf("key=%s data=%s\\n", key, data);
+   free(data);
+ }
+ else
+   printf("key=%s not found\\n", key);
+
+ /* multiple record search */
+ struct cdb_find cdbf;
+ int n;
+ cdb_findinit(&cdbf, &cdb, key, keylen);
+ n = 0;
+ while(cdb_findnext(&cdbf) > 0) {
+   datalen = cdb_datalen(&cdb);
+   data = malloc(datalen + 1);
+   cdb_read(&cdb, data, datalen, cdb_datapos(&cdb));
+   data[datalen] = '\\0';
+   printf("key=%s data=%s\\n", key, data);
+   free(data);
+   ++n;
+ }
+ printf("key=%s %d records found\\n", n);
+
+ /* sequential database access */
+ unsigned pos;
+ int n;
+ cdb_seqinit(&cdb, &cpos);
+ n = 0;
+ while(cdb_seqnext(&cdb, &cpos) > 0) {
+   keylen = cdb_keylen(&cdb);
+   key = malloc(keylen + 1);
+   cdb_read(&cdb, key, keylen, cdb_keypos(&cdb));
+   key[keylen] = '\\0';
+   datalen = cdb_datalen(&cdb);
+   data = malloc(datalen + 1);
+   cdb_read(&cdb, data, datalen, cdb_datapos(&cdb));
+   data[datalen] = '\\0';
+   ++n;
+   printf("record %n: key=%s data=%s\\n", n, key, data);
+   free(data); free(key);
+ }
+ printf("total records found: %d\\n", n);
+
+ /* close the database */
+ cdb_free(&cdb);
+ close(fd);
+
+ /* simplistic query mode */
+ fd = open(filename, O_RDONLY);
+ if (cdb_seek(fd, key, keylen, &datalen) > 0) {
+   data = malloc(datalen + 1);
+   cdb_bread(fd, data, datalen);
+   data[datalen] = '\\0';
+   printf("key=%s data=%s\\n", key, data);
+ }
+ else
+   printf("key=%s not found\\n", key);
+ close(fd);
+.fi
+
+.SS "Create Mode"
+
+.nf
+ int fd;
+ struct cdb_make cdbm;
+ char *key, *data;
+ unsigned keylen, datalen;
+
+ /* initialize the database */
+ fd = open(filename, O_RDWR|O_CREAT|O_TRUNC, 0644);
+ cdb_make_start(&cdbm, fd);
+
+ while(have_more_data()) {
+   /* initialize key and data */
+   if (cdb_make_exists(&cdbm, key, keylen) == 0)
+     cdb_make_add(&cdbm, key, keylen, data, datalen);
+   /* or use cdb_make_put() with appropriate flags */
+ }
+
+ /* finalize and close the database */
+ cdb_make_finish(&cdbm);
+ close(fd);
+.fi
+
+.SH "SEE ALSO"
+cdb(5), cdb(1), dbm(3), db(3), open(2).
+
+.SH AUTHOR
+The \fBtinycdb\fR package written by Michael Tokarev <mjt@corpit.ru>,
+based on ideas and shares file format with original cdb library by
+Dan Bernstein.
+
+.SH LICENSE
+Public domain.
+.\" $Id: cdb.5,v 1.1 2005-04-10 22:17:14 mjt Exp $
+.\" cdb file format manpage
+.\"
+.\" This file is a part of tinycdb package by Michael Tokarev, mjt@corpit.ru.
+.\" Public domain.
+.\"
+.TH cdb 5 "Apr, 2005"
+
+.SH NAME
+cdb \- Constant DataBase file format
+
+.SH DESCRIPTION
+
+A \fBcdb\fR database is a single file used to map `keys'
+to `values', having records of (key,value) pairs.  File
+consists of 3 parts: toc (table of contents), data and
+index (hash tables).
+
+Toc has fixed length of 2048 bytes, containing 256 pointers
+to hash tables inside index sections.  Every pointer consists
+of position of a hash table in bytes from the beginning of
+a file, and a size of a hash table in entries, both are
+4-bytes (32 bits) unsigned integers in little-endian form.
+Hash table length may have zero length, meaning that
+corresponding hash table is empty.
+
+Right after toc section, data section follows without any
+alingment.  It consists of series of records, each is a
+key length, value (data) length, key and value.  Again,
+key and value length are 4-byte unsigned integers.  Each
+next record follows previous without any special alignment.
+
+After data section, index (hash tables) section follows.
+It should be looked to in conjunction with toc section,
+where each of max 256 hash tables are defined.  Index
+section consists of series of hash tables, with starting
+position and length defined in toc section.  Every hash
+table is a sequence of records each holds two numbers:
+key's hash value and record position inside data section
+(bytes from the beginning of a file to first byte of
+key length starting data record).  If record position
+is zero, then this is an empty hash table slot, pointed
+to nowhere.
+
+CDB hash function is
+.nf
+  hv = ((hv << 5) + hv) ^ \fIc\fR
+.fi
+for every single \fIc\fR byte of a key, starting with
+hv = \fI5381\fR.
+
+Toc section indexed by (hv % 256), i.e. hash value modulo
+256 (number of entries in toc section).
+
+In order to find a record, one should: first, compute the hash
+value (hv) of a key.  Second, look to hash table number hv modulo
+256.  If it is empty, then there is no such key exists.  If it
+is not empty, then third, loop by slots inside that hash table,
+starting from slot with number hv divided by 256 modulo length
+of that table, or ((hv / 256) % htlen), searching for this hv
+in hash table.  Stop search on empty slot (if record position
+is zero) or when all slots was probed (note cyclic search,
+jumping from end to beginning of a table).  When hash value in
+question is found in hash table, look to key of corresponding
+record, comparing it with key in question.  If them of the same
+length and equals to each other, then record is found, overwise,
+repeat with next hash table slot.  Note that there may be several
+records with the same key.
+
+.SH SEE ALSO
+cdb(1), cdb(3).
+
+.SH AUTHOR
+The \fBtinycdb\fR package written by Michael Tokarev <mjt@corpit.ru>,
+based on ideas and shares file format with original cdb library by
+Dan Bernstein.
+
+.SH LICENSE
+Public domain.
+/* $Id: cdb.c,v 1.18 2008-11-06 22:37:33 mjt Exp $
+ * cdb command line tool
+ *
+ * This file is a part of tinycdb package by Michael Tokarev, mjt@corpit.ru.
+ * Public domain.
+ */
+
+#define _GNU_SOURCE	/* #define this even on Windows */
+
+#ifdef _WIN32		/* by the way, how about win64? */
+# include <io.h>
+# include <malloc.h>
+# pragma warning(disable: 4996)
+#else
+# include <unistd.h>
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdarg.h>
+#include <fcntl.h>
+#include <errno.h>
+#include "cdb.h"
+
+#ifndef EPROTO
+# define EPROTO EINVAL
+#endif
+
+#ifdef __GLIBC__
+# define HAVE_PROGRAM_INVOCATION_SHORT_NAME
+#endif
+
+#ifdef HAVE_PROGRAM_INVOCATION_SHORT_NAME
+# define progname program_invocation_short_name
+#else
+static char *progname;
+#endif
+
+#ifndef O_NOFOLLOW
+# define O_NOFOLLOW 0
+#endif
+
+#ifdef _WIN32
+# define FBINMODE "b"
+#else
+# define FBINMODE
+#endif
+
+#define F_DUPMASK	0x000f
+#define F_WARNDUP	0x0100
+#define F_ERRDUP	0x0200
+#define F_MAP		0x1000	/* map format (or else CDB native format) */
+
+/* Silly defines just to suppress silly compiler warnings.
+ * The thing is, trivial routines like strlen(), fgets() etc expects
+ * char* argument, and GCC>=4 complains about using unsigned char* here.
+ * Silly silly silly.
+ */
+#ifdef __GNUC__
+static inline size_t ustrlen(const unsigned char *s) {
+  return strlen((const char*)s);
+}
+static inline unsigned char *ufgets(unsigned char *s, int size, FILE *f) {
+  return (unsigned char*)fgets((char*)s, size, f);
+}
+#else
+# define ustrlen strlen
+# define ufgets fgets
+#endif
+
+static unsigned char *buf;
+static unsigned blen;
+
+static void
+#ifdef __GNUC__
+__attribute__((noreturn,format(printf,2,3)))
+#endif
+error(int errnum, const char *fmt, ...)
+{
+  if (fmt) {
+    va_list ap;
+    fprintf(stderr, "%s: ", progname);
+    va_start(ap, fmt);
+    vfprintf(stderr, fmt, ap);
+    va_end(ap);
+  }
+  if (errnum)
+    fprintf(stderr, ": %s\n", strerror(errnum));
+  else {
+    if (fmt) putc('\n', stderr);
+    fprintf(stderr, "%s: try `%s -h' for help\n", progname, progname);
+  }
+  fflush(stderr);
+  exit(errnum ? 111 : 2);
+}
+
+static void allocbuf(unsigned len) {
+  if (blen < len) {
+    buf = (unsigned char*)(buf ? realloc(buf, len) : malloc(len));
+    if (!buf)
+      error(ENOMEM, "unable to allocate %u bytes", len);
+    blen = len;
+  }
+}
+
+static int qmode(char *dbname, const char *key, int num, int flags)
+{
+  struct cdb c;
+  struct cdb_find cf;
+  int r;
+  int n, found;
+
+  r = open(dbname, O_RDONLY);
+  if (r < 0 || cdb_init(&c, r) != 0)
+    error(errno, "unable to open database `%s'", dbname);
+
+  r = cdb_findinit(&cf, &c, key, strlen(key));
+  if (!r)
+    return 100;
+  else if (r < 0)
+    error(errno, "%s", key);
+  n = 0; found = 0;
+  while((r = cdb_findnext(&cf)) > 0) {
+    ++n;
+    if (num && num != n) continue;
+    ++found;
+    allocbuf(cdb_datalen(&c));
+    if (cdb_read(&c, buf, cdb_datalen(&c), cdb_datapos(&c)) != 0)
+      error(errno, "unable to read value");
+    fwrite(buf, 1, cdb_datalen(&c), stdout);
+    if (flags & F_MAP) putchar('\n');
+    if (num)
+      break;
+  }
+  if (r < 0)
+    error(0, "%s", key);
+  return found ? 0 : 100;
+}
+
+static void
+fget(FILE *f, unsigned char *b, unsigned len, unsigned *posp, unsigned limit)
+{
+  if (posp && limit - *posp < len)
+    error(EPROTO, "invalid database format");
+  if (fread(b, 1, len, f) != len) {
+    if (ferror(f)) error(errno, "unable to read");
+    fprintf(stderr, "%s: unable to read: short file\n", progname);
+    exit(2);
+  }
+  if (posp) *posp += len;
+}
+
+static int
+fcpy(FILE *fi, FILE *fo, unsigned len, unsigned *posp, unsigned limit)
+{
+  while(len > blen) {
+    fget(fi, buf, blen, posp, limit);
+    if (fo && fwrite(buf, 1, blen, fo) != blen) return -1;
+    len -= blen;
+  }
+  if (len) {
+    fget(fi, buf, len, posp, limit);
+    if (fo && fwrite(buf, 1, len, fo) != len) return -1;
+  }
+  return 0;
+}
+
+static int
+dmode(char *dbname, char mode, int flags)
+{
+  unsigned eod, klen, vlen;
+  unsigned pos = 0;
+  FILE *f;
+  if (strcmp(dbname, "-") == 0)
+    f = stdin;
+  else if ((f = fopen(dbname, "r" FBINMODE)) == NULL)
+    error(errno, "open %s", dbname);
+  allocbuf(2048);
+  fget(f, buf, 2048, &pos, 2048);
+  eod = cdb_unpack(buf);
+  while(pos < eod) {
+    fget(f, buf, 8, &pos, eod);
+    klen = cdb_unpack(buf);
+    vlen = cdb_unpack(buf + 4);
+    if (!(flags & F_MAP))
+      if (printf(mode == 'd' ? "+%u,%u:" : "+%u:", klen, vlen) < 0) return -1;
+    if (fcpy(f, stdout, klen, &pos, eod) != 0) return -1;
+    if (mode == 'd')
+      if (fputs(flags & F_MAP ? " " : "->", stdout) < 0)
+        return -1;
+    if (fcpy(f, mode == 'd' ? stdout : NULL, vlen, &pos, eod) != 0)
+      return -1;
+    if (putc('\n', stdout) < 0)
+      return -1;
+  }
+  if (pos != eod)
+    error(EPROTO, "invalid cdb file format");
+  if (!(flags & F_MAP))
+    if (putc('\n', stdout) < 0)
+      return -1;
+  return 0;
+}
+
+static int smode(char *dbname) {
+  FILE *f;
+  unsigned pos, eod;
+  unsigned cnt = 0;
+  unsigned kmin = 0, kmax = 0, ktot = 0;
+  unsigned vmin = 0, vmax = 0, vtot = 0;
+  unsigned hmin = 0, hmax = 0, htot = 0, hcnt = 0;
+#define NDIST 11
+  unsigned dist[NDIST];
+  unsigned char toc[2048];
+  unsigned k;
+
+  if (strcmp(dbname, "-") == 0)
+    f = stdin;
+  else if ((f = fopen(dbname, "r" FBINMODE)) == NULL)
+    error(errno, "open %s", dbname);
+
+  pos = 0;
+  fget(f, toc, 2048, &pos, 2048);
+
+  allocbuf(2048);
+
+  eod = cdb_unpack(toc);
+  while(pos < eod) {
+    unsigned klen, vlen;
+    fget(f, buf, 8, &pos, eod);
+    klen = cdb_unpack(buf);
+    vlen = cdb_unpack(buf + 4);
+    fcpy(f, NULL, klen, &pos, eod);
+    fcpy(f, NULL, vlen, &pos, eod);
+    ++cnt;
+    ktot += klen;
+    if (!kmin || kmin > klen) kmin = klen;
+    if (kmax < klen) kmax = klen;
+    vtot += vlen;
+    if (!vmin || vmin > vlen) vmin = vlen;
+    if (vmax < vlen) vmax = vlen;
+    vlen += klen;
+  }
+  if (pos != eod) error(EPROTO, "invalid cdb file format");
+
+  for (k = 0; k < NDIST; ++k)
+    dist[k] = 0;
+  for (k = 0; k < 256; ++k) {
+    unsigned i = cdb_unpack(toc + (k << 3));
+    unsigned hlen = cdb_unpack(toc + (k << 3) + 4);
+    if (i != pos) error(EPROTO, "invalid cdb hash table");
+    if (!hlen) continue;
+    for (i = 0; i < hlen; ++i) {
+      unsigned h;
+      fget(f, buf, 8, &pos, 0xffffffff);
+      if (!cdb_unpack(buf + 4)) continue;
+      h = (cdb_unpack(buf) >> 8) % hlen;
+      if (h == i) h = 0;
+      else {
+        if (h < i) h = i - h;
+        else h = hlen - h + i;
+        if (h >= NDIST) h = NDIST - 1;
+      }
+      ++dist[h];
+    }
+    if (!hmin || hmin > hlen) hmin = hlen;
+    if (hmax < hlen) hmax = hlen;
+    htot += hlen;
+    ++hcnt;
+  }
+  printf("number of records: %u\n", cnt);
+  printf("key min/avg/max length: %u/%u/%u\n",
+         kmin, cnt ? (ktot + cnt / 2) / cnt : 0, kmax);
+  printf("val min/avg/max length: %u/%u/%u\n",
+         vmin, cnt ? (vtot + cnt / 2) / cnt : 0, vmax);
+  printf("hash tables/entries/collisions: %u/%u/%u\n",
+         hcnt, htot, cnt - dist[0]);
+  printf("hash table min/avg/max length: %u/%u/%u\n",
+         hmin, hcnt ? (htot + hcnt / 2) / hcnt : 0, hmax);
+  printf("hash table distances:\n");
+  for(k = 0; k < NDIST; ++k)
+    printf(" %c%u: %6u %2u%%\n",
+           k == NDIST - 1 ? '>' : 'd', k == NDIST - 1 ? k - 1 : k,
+           dist[k], cnt ? dist[k] * 100 / cnt : 0);
+  return 0;
+}
+
+static void badinput(const char *fn) {
+  fprintf(stderr, "%s: %s: bad format\n", progname, fn);
+  exit(2);
+}
+
+static int getnum(FILE *f, unsigned *np, const char *fn) {
+  unsigned n;
+  int c = getc(f);
+  if (c < '0' || c > '9') badinput(fn);
+  n = c - '0';
+  while((c = getc(f)) >= '0' && c <= '9') {
+    c -= '0';
+    if (0xffffffff / 10 - c < n) badinput(fn);
+    n = n * 10 + c;
+  }
+  *np = n;
+  return c;
+}
+
+static void
+addrec(struct cdb_make *cdbmp,
+       const unsigned char *key, unsigned klen,
+       const unsigned char *val, unsigned vlen,
+       int flags)
+{
+  int r = cdb_make_put(cdbmp, key, klen, val, vlen, flags & F_DUPMASK);
+  if (r < 0)
+    error(errno, "cdb_make_put");
+  else if (r && (flags & F_WARNDUP)) {
+    fprintf(stderr, "%s: key `", progname);
+    fwrite(key, 1, klen, stderr);
+    fputs("' duplicated\n", stderr);
+    if (flags & F_ERRDUP)
+      exit(1);
+  }
+}
+
+static void
+dofile_cdb(struct cdb_make *cdbmp, FILE *f, const char *fn, int flags)
+{
+  unsigned klen, vlen;
+  int c;
+  while((c = getc(f)) == '+') {
+    if ((c = getnum(f, &klen, fn)) != ',' ||
+        (c = getnum(f, &vlen, fn)) != ':' ||
+        0xffffffff - klen < vlen)
+      badinput(fn);
+    allocbuf(klen + vlen);
+    fget(f, buf, klen, NULL, 0);
+    if (getc(f) != '-' || getc(f) != '>') badinput(fn);
+    fget(f, buf + klen, vlen, NULL, 0);
+    if (getc(f) != '\n') badinput(fn);
+    addrec(cdbmp, buf, klen, buf + klen, vlen, flags);
+  }
+  if (c != '\n') badinput(fn);
+}
+
+static void
+dofile_ln(struct cdb_make *cdbmp, FILE *f, int flags)
+{
+  unsigned char *k, *v;
+  while(ufgets(buf, blen, f) != NULL) {
+    unsigned l = 0;
+    for (;;) {
+      l += ustrlen(buf + l);
+      v = buf + l;
+      if (v > buf && v[-1] == '\n') {
+        v[-1] = '\0';
+        break;
+      }
+      if (l < blen)
+        allocbuf(l + 512);
+      if (!ufgets(buf + l, blen - l, f))
+        break;
+    }
+    k = buf;
+    while(*k == ' ' || *k == '\t') ++k;
+    if (!*k || *k == '#')
+      continue;
+    v = k;
+    while(*v && *v != ' ' && *v != '\t') ++v;
+    if (*v) *v++ = '\0';
+    while(*v == ' ' || *v == '\t') ++v;
+    addrec(cdbmp, k, ustrlen(k), v, ustrlen(v), flags);
+  }
+}
+
+static void
+dofile(struct cdb_make *cdbmp, FILE *f, const char *fn, int flags)
+{
+  if (flags & F_MAP)
+    dofile_ln(cdbmp, f, flags);
+  else
+    dofile_cdb(cdbmp, f, fn, flags);
+  if (ferror(f))
+    error(errno, "read error");
+}
+
+static int
+cmode(char *dbname, char *tmpname, int argc, char **argv, int flags, int perms)
+{
+  struct cdb_make cdb;
+  int fd;
+  if (!tmpname) {
+    tmpname = (char*)malloc(strlen(dbname) + 5);
+    if (!tmpname)
+      error(ENOMEM, "unable to allocate memory");
+    /* OpenBSD compiler complains about strcat() and strcpy() usage,
+     * and suggests to replace them with (non-standard) strlcat() and
+     * strlcpy().  This is silly, since it's obvious that usage of
+     * original str*() routines here is correct.
+     * This is compiler/environment bug, not tinycdb bug, so please
+     * fix it in proper place, and don't send patches to me.  Thank you.
+     */
+    strcat(strcpy(tmpname, dbname), ".tmp");
+  }
+  else if (strcmp(tmpname, "-") == 0 || strcmp(tmpname, dbname) == 0)
+    tmpname = dbname;
+  if (perms >= 0)
+    umask(0);
+  unlink(tmpname);
+  fd = open(tmpname, O_RDWR|O_CREAT|O_EXCL|O_NOFOLLOW,
+            perms >= 0 ? perms : 0666);
+  if (fd < 0)
+    error(errno, "unable to create %s", tmpname);
+  cdb_make_start(&cdb, fd);
+  allocbuf(4096);
+  if (argc) {
+    int i;
+    for (i = 0; i < argc; ++i) {
+      if (strcmp(argv[i], "-") == 0)
+        dofile(&cdb, stdin, "(stdin)", flags);
+      else {
+        FILE *f = fopen(argv[i], "r");
+        if (!f)
+          error(errno, "%s", argv[i]);
+        dofile(&cdb, f, argv[i], flags);
+        fclose(f);
+      }
+    }
+  }
+  else
+    dofile(&cdb, stdin, "(stdin)", flags);
+  if (cdb_make_finish(&cdb) != 0)
+    error(errno, "cdb_make_finish");
+  close(fd);
+  if (tmpname != dbname)
+    if (rename(tmpname, dbname) != 0)
+      error(errno, "rename %s->%s", tmpname, dbname);
+  return 0;
+}
+
+int main(int argc, char **argv)
+{
+  int c;
+  char mode = 0;
+  char *tmpname = NULL;
+  int flags = 0;
+  int num = 0;
+  int r;
+  int perms = -1;
+  extern char *optarg;
+  extern int optind;
+
+#ifdef HAVE_PROGRAM_INVOCATION_SHORT_NAME
+  argv[0] = progname;
+#else
+  if (argv[0] && (progname = strrchr(argv[0], '/')) != NULL)
+    argv[0] = ++progname;
+  else
+    progname = argv[0];
+#endif
+
+  if (argc <= 1)
+    error(0, "no arguments given");
+
+  while((c = getopt(argc, argv, "qdlcsht:n:mwruep:0")) != EOF)
+    switch(c) {
+    case 'q': case 'd':  case 'l': case 'c': case 's':
+      if (mode && mode != c)
+        error(0, "different modes of operation requested");
+      mode = c;
+      break;
+    case 't': tmpname = optarg; break;
+    case 'w': flags |= F_WARNDUP; break;
+    case 'e': flags |= F_WARNDUP | F_ERRDUP; break;
+    case 'r': flags = (flags & ~F_DUPMASK) | CDB_PUT_REPLACE; break;
+    case 'u': flags = (flags & ~F_DUPMASK) | CDB_PUT_INSERT; break;
+    case '0': flags = (flags & ~F_DUPMASK) | CDB_PUT_REPLACE0; break;
+    case 'm': flags |= F_MAP; break;
+    case 'p': {
+      char *ep = NULL;
+      perms = strtol(optarg, &ep, 0);
+      if (perms < 0 || perms > 0777 || (ep && *ep))
+        error(0, "invalid permissions `%s'", optarg);
+      break;
+    }
+    case 'n': {
+      char *ep = NULL;
+      if ((num = strtol(optarg, &ep, 0)) <= 0 || (ep && *ep))
+        error(0, "invalid record number `%s'", optarg);
+      break;
+    }
+    case 'h':
+#define strify(x) _strify(x)
+#define _strify(x) #x
+      printf("\
+%s: Constant DataBase (CDB) tool version " strify(TINYCDB_VERSION)
+". Usage is:\n\
+ query:  %s -q [-m] [-n recno|-a] cdbfile key\n\
+ dump:   %s -d [-m] [cdbfile|-]\n\
+ list:   %s -l [-m] [cdbfile|-]\n\
+ create: %s -c [-m] [-wrue0] [-t tempfile|-] [-p perms] cdbfile [infile...]\n\
+ stats:  %s -s [cdbfile|-]\n\
+ help:   %s -h\n\
+", progname, progname, progname, progname, progname, progname, progname);
+      return 0;
+
+    default:
+      error(0, NULL);
+    }
+
+  argv += optind;
+  argc -= optind;
+  switch(mode) {
+    case 'q':
+      if (argc < 2) error(0, "no database or key to query specified");
+      if (argc > 2) error(0, "extra arguments in command line");
+      r = qmode(argv[0], argv[1], num, flags);
+      break;
+    case 'c':
+      if (!argc) error(0, "no database name specified");
+      if ((flags & F_WARNDUP) && !(flags & F_DUPMASK))
+        flags |= CDB_PUT_WARN;
+      r = cmode(argv[0], tmpname, argc - 1, argv + 1, flags, perms);
+      break;
+    case 'd':
+    case 'l':
+      if (argc > 1) error(0, "extra arguments for dump/list");
+      r = dmode(argc ? argv[0] : "-", mode, flags);
+      break;
+    case 's':
+      if (argc > 1) error(0, "extra argument(s) for stats");
+      r = smode(argc ? argv[0] : "-");
+      break;
+    default:
+      error(0, "no -q, -c, -d, -l or -s option specified");
+  }
+  if (r < 0 || fflush(stdout) < 0)
+    error(errno, "unable to write: %d", c);
+  return r;
+}
+
+/* $Id: cdb.h,v 1.10 2009-01-31 17:12:22 mjt Exp $
+ * public cdb include file
+ *
+ * This file is a part of tinycdb package by Michael Tokarev, mjt@corpit.ru.
+ * Public domain.
+ */
+
+#ifndef TINYCDB_VERSION
+#define TINYCDB_VERSION 0.77
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef unsigned int cdbi_t; /* compatibility */
+
+/* common routines */
+unsigned cdb_hash(const void *buf, unsigned len);
+unsigned cdb_unpack(const unsigned char buf[4]);
+void cdb_pack(unsigned num, unsigned char buf[4]);
+
+struct cdb {
+  int cdb_fd;			/* file descriptor */
+  /* private members */
+  unsigned cdb_fsize;		/* datafile size */
+  unsigned cdb_dend;		/* end of data ptr */
+  const unsigned char *cdb_mem; /* mmap'ed file memory */
+  unsigned cdb_vpos, cdb_vlen;	/* found data */
+  unsigned cdb_kpos, cdb_klen;	/* found key */
+};
+
+#define CDB_STATIC_INIT {0,0,0,0,0,0,0,0}
+
+#define cdb_datapos(c) ((c)->cdb_vpos)
+#define cdb_datalen(c) ((c)->cdb_vlen)
+#define cdb_keypos(c) ((c)->cdb_kpos)
+#define cdb_keylen(c) ((c)->cdb_klen)
+#define cdb_fileno(c) ((c)->cdb_fd)
+
+int cdb_init(struct cdb *cdbp, int fd);
+void cdb_free(struct cdb *cdbp);
+
+int cdb_read(const struct cdb *cdbp,
+             void *buf, unsigned len, unsigned pos);
+#define cdb_readdata(cdbp, buf) \
+        cdb_read((cdbp), (buf), cdb_datalen(cdbp), cdb_datapos(cdbp))
+#define cdb_readkey(cdbp, buf) \
+        cdb_read((cdbp), (buf), cdb_keylen(cdbp), cdb_keypos(cdbp))
+
+const void *cdb_get(const struct cdb *cdbp, unsigned len, unsigned pos);
+#define cdb_getdata(cdbp) \
+        cdb_get((cdbp), cdb_datalen(cdbp), cdb_datapos(cdbp))
+#define cdb_getkey(cdbp) \
+        cdb_get((cdbp), cdb_keylen(cdbp), cdb_keypos(cdbp))
+
+int cdb_find(struct cdb *cdbp, const void *key, unsigned klen);
+
+struct cdb_find {
+  struct cdb *cdb_cdbp;
+  unsigned cdb_hval;
+  const unsigned char *cdb_htp, *cdb_htab, *cdb_htend;
+  unsigned cdb_httodo;
+  const void *cdb_key;
+  unsigned cdb_klen;
+};
+
+int cdb_findinit(struct cdb_find *cdbfp, struct cdb *cdbp,
+                 const void *key, unsigned klen);
+int cdb_findnext(struct cdb_find *cdbfp);
+
+#define cdb_seqinit(cptr, cdbp) ((*(cptr))=2048)
+int cdb_seqnext(unsigned *cptr, struct cdb *cdbp);
+
+/* old simple interface */
+/* open file using standard routine, then: */
+int cdb_seek(int fd, const void *key, unsigned klen, unsigned *dlenp);
+int cdb_bread(int fd, void *buf, int len);
+
+/* cdb_make */
+
+struct cdb_make {
+  int cdb_fd;			/* file descriptor */
+  /* private */
+  unsigned cdb_dpos;		/* data position so far */
+  unsigned cdb_rcnt;		/* record count so far */
+  unsigned char cdb_buf[4096];	/* write buffer */
+  unsigned char *cdb_bpos;	/* current buf position */
+  struct cdb_rl *cdb_rec[256];	/* list of arrays of record infos */
+};
+
+enum cdb_put_mode {
+  CDB_PUT_ADD = 0,	/* add unconditionnaly, like cdb_make_add() */
+#define CDB_PUT_ADD	CDB_PUT_ADD
+  CDB_FIND = CDB_PUT_ADD,
+  CDB_PUT_REPLACE,	/* replace: do not place to index OLD record */
+#define CDB_PUT_REPLACE	CDB_PUT_REPLACE
+  CDB_FIND_REMOVE = CDB_PUT_REPLACE,
+  CDB_PUT_INSERT,	/* add only if not already exists */
+#define CDB_PUT_INSERT	CDB_PUT_INSERT
+  CDB_PUT_WARN,		/* add unconditionally but ret. 1 if exists */
+#define CDB_PUT_WARN	CDB_PUT_WARN
+  CDB_PUT_REPLACE0,	/* if a record exists, fill old one with zeros */
+#define CDB_PUT_REPLACE0 CDB_PUT_REPLACE0
+  CDB_FIND_FILL0 = CDB_PUT_REPLACE0
+};
+
+int cdb_make_start(struct cdb_make *cdbmp, int fd);
+int cdb_make_add(struct cdb_make *cdbmp,
+                 const void *key, unsigned klen,
+                 const void *val, unsigned vlen);
+int cdb_make_exists(struct cdb_make *cdbmp,
+                    const void *key, unsigned klen);
+int cdb_make_find(struct cdb_make *cdbmp,
+                  const void *key, unsigned klen,
+                  enum cdb_put_mode mode);
+int cdb_make_put(struct cdb_make *cdbmp,
+                 const void *key, unsigned klen,
+                 const void *val, unsigned vlen,
+                 enum cdb_put_mode mode);
+int cdb_make_finish(struct cdb_make *cdbmp);
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+
+#endif /* include guard */
+/* $Id: cdb_find.c,v 1.8 2003-11-03 16:42:41 mjt Exp $
+ * cdb_find routine
+ *
+ * This file is a part of tinycdb package by Michael Tokarev, mjt@corpit.ru.
+ * Public domain.
+ */
+
+#include "cdb_int.h"
+
+int
+cdb_find(struct cdb *cdbp, const void *key, unsigned klen)
+{
+  const unsigned char *htp;	/* hash table pointer */
+  const unsigned char *htab;	/* hash table */
+  const unsigned char *htend;	/* end of hash table */
+  unsigned httodo;		/* ht bytes left to look */
+  unsigned pos, n;
+
+  unsigned hval;
+
+  if (klen >= cdbp->cdb_dend)	/* if key size is too large */
+    return 0;
+
+  hval = cdb_hash(key, klen);
+
+  /* find (pos,n) hash table to use */
+  /* first 2048 bytes (toc) are always available */
+  /* (hval % 256) * 8 */
+  htp = cdbp->cdb_mem + ((hval << 3) & 2047); /* index in toc (256x8) */
+  n = cdb_unpack(htp + 4);	/* table size */
+  if (!n)			/* empty table */
+    return 0;			/* not found */
+  httodo = n << 3;		/* bytes of htab to lookup */
+  pos = cdb_unpack(htp);	/* htab position */
+  if (n > (cdbp->cdb_fsize >> 3) /* overflow of httodo ? */
+      || pos < cdbp->cdb_dend /* is htab inside data section ? */
+      || pos > cdbp->cdb_fsize /* htab start within file ? */
+      || httodo > cdbp->cdb_fsize - pos) /* entrie htab within file ? */
+    return errno = EPROTO, -1;
+
+  htab = cdbp->cdb_mem + pos;	/* htab pointer */
+  htend = htab + httodo;	/* after end of htab */
+  /* htab starting position: rest of hval modulo htsize, 8bytes per elt */
+  htp = htab + (((hval >> 8) % n) << 3);
+
+  for(;;) {
+    pos = cdb_unpack(htp + 4);	/* record position */
+    if (!pos)
+      return 0;
+    if (cdb_unpack(htp) == hval) {
+      if (pos > cdbp->cdb_dend - 8) /* key+val lengths */
+	return errno = EPROTO, -1;
+      if (cdb_unpack(cdbp->cdb_mem + pos) == klen) {
+	if (cdbp->cdb_dend - klen < pos + 8)
+	  return errno = EPROTO, -1;
+	if (memcmp(key, cdbp->cdb_mem + pos + 8, klen) == 0) {
+	  n = cdb_unpack(cdbp->cdb_mem + pos + 4);
+	  pos += 8;
+	  if (cdbp->cdb_dend < n || cdbp->cdb_dend - n < pos + klen)
+	    return errno = EPROTO, -1;
+	  cdbp->cdb_kpos = pos;
+	  cdbp->cdb_klen = klen;
+	  cdbp->cdb_vpos = pos + klen;
+	  cdbp->cdb_vlen = n;
+	  return 1;
+	}
+      }
+    }
+    httodo -= 8;
+    if (!httodo)
+      return 0;
+    if ((htp += 8) >= htend)
+      htp = htab;
+  }
+
+}

src/cdb_findnext.c

+/* $Id: cdb_findnext.c,v 1.9 2003-11-03 16:42:41 mjt Exp $
+ * sequential cdb_find routines
+ *
+ * This file is a part of tinycdb package by Michael Tokarev, mjt@corpit.ru.
+ * Public domain.
+ */
+
+/* see cdb_find.c for comments */
+
+#include "cdb_int.h"
+
+int
+cdb_findinit(struct cdb_find *cdbfp, struct cdb *cdbp,
+             const void *key, unsigned klen)
+{
+  unsigned n, pos;
+
+  cdbfp->cdb_cdbp = cdbp;
+  cdbfp->cdb_key = key;
+  cdbfp->cdb_klen = klen;
+  cdbfp->cdb_hval = cdb_hash(key, klen);
+
+  cdbfp->cdb_htp = cdbp->cdb_mem + ((cdbfp->cdb_hval << 3) & 2047);
+  n = cdb_unpack(cdbfp->cdb_htp + 4);
+  cdbfp->cdb_httodo = n << 3;
+  if (!n)
+    return 0;
+  pos = cdb_unpack(cdbfp->cdb_htp);
+  if (n > (cdbp->cdb_fsize >> 3)
+      || pos < cdbp->cdb_dend
+      || pos > cdbp->cdb_fsize
+      || cdbfp->cdb_httodo > cdbp->cdb_fsize - pos)
+    return errno = EPROTO, -1;
+
+  cdbfp->cdb_htab = cdbp->cdb_mem + pos;
+  cdbfp->cdb_htend = cdbfp->cdb_htab + cdbfp->cdb_httodo;
+  cdbfp->cdb_htp = cdbfp->cdb_htab + (((cdbfp->cdb_hval >> 8) % n) << 3);
+
+  return 1;
+}
+
+int
+cdb_findnext(struct cdb_find *cdbfp) {
+  struct cdb *cdbp = cdbfp->cdb_cdbp;
+  unsigned pos, n;
+  unsigned klen = cdbfp->cdb_klen;
+
+  while(cdbfp->cdb_httodo) {
+    pos = cdb_unpack(cdbfp->cdb_htp + 4);
+    if (!pos)
+      return 0;
+    n = cdb_unpack(cdbfp->cdb_htp) == cdbfp->cdb_hval;
+    if ((cdbfp->cdb_htp += 8) >= cdbfp->cdb_htend)
+      cdbfp->cdb_htp = cdbfp->cdb_htab;
+    cdbfp->cdb_httodo -= 8;
+    if (n) {
+      if (pos > cdbp->cdb_fsize - 8)
+	return errno = EPROTO, -1;
+      if (cdb_unpack(cdbp->cdb_mem + pos) == klen) {
+	if (cdbp->cdb_fsize - klen < pos + 8)
+	  return errno = EPROTO, -1;
+	if (memcmp(cdbfp->cdb_key,
+	    cdbp->cdb_mem + pos + 8, klen) == 0) {
+	  n = cdb_unpack(cdbp->cdb_mem + pos + 4);
+	  pos += 8;
+	  if (cdbp->cdb_fsize < n ||
+              cdbp->cdb_fsize - n < pos + klen)
+	    return errno = EPROTO, -1;
+	  cdbp->cdb_kpos = pos;
+	  cdbp->cdb_klen = klen;
+	  cdbp->cdb_vpos = pos + klen;
+	  cdbp->cdb_vlen = n;
+	  return 1;
+	}
+      }
+    }
+  }
+
+  return 0;
+
+}
+/* $Id: cdb_hash.c,v 1.5 2003-11-03 16:42:41 mjt Exp $
+ * cdb hashing routine
+ *
+ * This file is a part of tinycdb package by Michael Tokarev, mjt@corpit.ru.
+ * Public domain.
+ */
+
+#include "cdb.h"
+