Add '.ext/cfd/' from commit '09d274abdda494424271ffff4f83ee5a58cb262e'
authorMark Wooding <mdw@distorted.org.uk>
Sun, 23 Apr 2023 21:24:17 +0000 (22:24 +0100)
committerMark Wooding <mdw@distorted.org.uk>
Sun, 23 Apr 2023 21:24:17 +0000 (22:24 +0100)
git-subtree-dir: .ext/cfd
git-subtree-mainline: 46c7a9ab1e830a9c9ba2d607fdf48ed7554428bb
git-subtree-split: 09d274abdda494424271ffff4f83ee5a58cb262e

252 files changed:
.ext/cfd/.skelrc [new file with mode: 0644]
.ext/cfd/build/auto-version [moved from build/auto-version with 100% similarity]
.ext/cfd/build/autotest.am [moved from build/autotest.am with 100% similarity]
.ext/cfd/build/confsubst [moved from build/confsubst with 100% similarity]
.ext/cfd/build/maninst [moved from build/maninst with 100% similarity]
.ext/cfd/build/mdwsetup.py [moved from build/mdwsetup.py with 100% similarity]
.ext/cfd/build/pysetup.mk [moved from build/pysetup.mk with 100% similarity]
.ext/cfd/build/testsuite.at [moved from build/testsuite.at with 100% similarity]
.ext/cfd/doc/INSTALL [moved from doc/INSTALL with 100% similarity]
.ext/cfd/doc/texinice.tex [moved from doc/texinice.tex with 100% similarity]
.ext/cfd/licence/AGPL-3 [moved from licence/AGPL-3 with 100% similarity]
.ext/cfd/licence/GPL-1 [moved from licence/GPL-1 with 100% similarity]
.ext/cfd/licence/GPL-2 [moved from licence/GPL-2 with 100% similarity]
.ext/cfd/licence/GPL-3 [moved from licence/GPL-3 with 100% similarity]
.ext/cfd/licence/LGPL-2 [moved from licence/LGPL-2 with 100% similarity]
.ext/cfd/licence/LGPL-2.1 [moved from licence/LGPL-2.1 with 100% similarity]
.ext/cfd/licence/LGPL-3 [moved from licence/LGPL-3 with 100% similarity]
.ext/cfd/licence/agpl-3.0.tex [moved from licence/agpl-3.0.tex with 100% similarity]
.ext/cfd/licence/agpl-3.0.texi [moved from licence/agpl-3.0.texi with 100% similarity]
.ext/cfd/licence/gpl-2.0.tex [moved from licence/gpl-2.0.tex with 100% similarity]
.ext/cfd/licence/gpl-2.0.texi [moved from licence/gpl-2.0.texi with 100% similarity]
.ext/cfd/licence/gpl-3.0.tex [moved from licence/gpl-3.0.tex with 100% similarity]
.ext/cfd/licence/gpl-3.0.texi [moved from licence/gpl-3.0.texi with 100% similarity]
.ext/cfd/licence/gpl.texi [moved from licence/gpl.texi with 100% similarity]
.ext/cfd/licence/latex-licence-test.tex [moved from licence/latex-licence-test.tex with 100% similarity]
.ext/cfd/licence/lgpl-2.0.tex [moved from licence/lgpl-2.0.tex with 100% similarity]
.ext/cfd/licence/lgpl-2.0.texi [moved from licence/lgpl-2.0.texi with 100% similarity]
.ext/cfd/licence/lgpl-2.1.tex [moved from licence/lgpl-2.1.tex with 100% similarity]
.ext/cfd/licence/lgpl-2.1.texi [moved from licence/lgpl-2.1.texi with 100% similarity]
.ext/cfd/licence/lgpl-3.0.tex [moved from licence/lgpl-3.0.tex with 100% similarity]
.ext/cfd/licence/lgpl-3.0.texi [moved from licence/lgpl-3.0.texi with 100% similarity]
.ext/cfd/licence/texinfo-licence-test.texi [moved from licence/texinfo-licence-test.texi with 100% similarity]
.ext/cfd/m4/mdw-auto-version.m4 [moved from m4/mdw-auto-version.m4 with 100% similarity]
.ext/cfd/m4/mdw-decl-environ.m4 [moved from m4/mdw-decl-environ.m4 with 100% similarity]
.ext/cfd/m4/mdw-define-paths.m4 [moved from m4/mdw-define-paths.m4 with 100% similarity]
.ext/cfd/m4/mdw-dir-texmf.m4 [moved from m4/mdw-dir-texmf.m4 with 100% similarity]
.ext/cfd/m4/mdw-libtool-version-info.m4 [moved from m4/mdw-libtool-version-info.m4 with 100% similarity]
.ext/cfd/m4/mdw-manext.m4 [moved from m4/mdw-manext.m4 with 100% similarity]
.ext/cfd/m4/mdw-silent-rules.m4 [moved from m4/mdw-silent-rules.m4 with 100% similarity]
.ext/cfd/src/getdate.h [moved from src/getdate.h with 100% similarity]
.ext/cfd/src/getdate.y [moved from src/getdate.y with 100% similarity]
.ext/cfd/src/mdwopt.c [moved from src/mdwopt.c with 100% similarity]
.ext/cfd/src/mdwopt.h [moved from src/mdwopt.h with 100% similarity]
.gitignore [new file with mode: 0644]
.links [new file with mode: 0644]
.mailmap [new file with mode: 0644]
.skelrc
Makefile.am [new file with mode: 0644]
README [new file with mode: 0644]
buf/Makefile.am [new file with mode: 0644]
buf/lbuf.3 [new file with mode: 0644]
buf/lbuf.c [new file with mode: 0644]
buf/lbuf.h [new file with mode: 0644]
buf/pkbuf.3 [new file with mode: 0644]
buf/pkbuf.c [new file with mode: 0644]
buf/pkbuf.h [new file with mode: 0644]
codec/Makefile.am [new file with mode: 0644]
codec/base32.h [new file with mode: 0644]
codec/base64.3 [new file with mode: 0644]
codec/base64.h [new file with mode: 0644]
codec/baseconv.c [new file with mode: 0644]
codec/bincode.1 [new file with mode: 0644]
codec/bincode.c [new file with mode: 0644]
codec/codec.3 [new file with mode: 0644]
codec/codec.c [new file with mode: 0644]
codec/codec.h [new file with mode: 0644]
codec/hex.h [new file with mode: 0644]
codec/null-codec.c [new file with mode: 0644]
codec/tests.at [new file with mode: 0644]
codec/url.3 [new file with mode: 0644]
codec/url.c [new file with mode: 0644]
codec/url.h [new file with mode: 0644]
configure.ac [new file with mode: 0644]
debian/.gitignore [new file with mode: 0644]
debian/changelog [new file with mode: 0644]
debian/clean [new file with mode: 0644]
debian/common.symbols [new file with mode: 0644]
debian/compat [new file with mode: 0644]
debian/control [new file with mode: 0644]
debian/copyright [new file with mode: 0644]
debian/mlib-bin.install [new file with mode: 0644]
debian/mlib-dev.install [new file with mode: 0644]
debian/mlib2-adns.copyright [new file with mode: 0644]
debian/mlib2-adns.install.in [new file with mode: 0644]
debian/mlib2-adns.symbols [new file with mode: 0644]
debian/mlib2.install [new file with mode: 0644]
debian/mlib2.symbols [new file with mode: 0644]
debian/rules [new file with mode: 0755]
debian/source/format [new file with mode: 0644]
hash/Makefile.am [new file with mode: 0644]
hash/crc-mktab.1 [new file with mode: 0644]
hash/crc-mktab.c [new file with mode: 0644]
hash/crc32.3 [new file with mode: 0644]
hash/crc32.c [new file with mode: 0644]
hash/crc32.h [new file with mode: 0644]
hash/t/crc32-test.c [new file with mode: 0644]
hash/t/crc32.tests [new file with mode: 0644]
hash/t/unihash-test.c [new file with mode: 0644]
hash/t/unihash-testgen.py [new file with mode: 0644]
hash/tests.at [new file with mode: 0644]
hash/unihash-mkstatic.1 [new file with mode: 0644]
hash/unihash-mkstatic.c [new file with mode: 0644]
hash/unihash.3 [new file with mode: 0644]
hash/unihash.c [new file with mode: 0644]
hash/unihash.h [new file with mode: 0644]
mLib.3 [new file with mode: 0644]
mLib.pc.in [new file with mode: 0644]
mem/Makefile.am [new file with mode: 0644]
mem/alloc.3 [new file with mode: 0644]
mem/alloc.c [new file with mode: 0644]
mem/alloc.h [new file with mode: 0644]
mem/arena.3 [new file with mode: 0644]
mem/arena.c [new file with mode: 0644]
mem/arena.h [new file with mode: 0644]
mem/pool-file.c [new file with mode: 0644]
mem/pool-sub.c [new file with mode: 0644]
mem/pool.3 [new file with mode: 0644]
mem/pool.c [new file with mode: 0644]
mem/pool.h [new file with mode: 0644]
mem/sub.3 [new file with mode: 0644]
mem/sub.c [new file with mode: 0644]
mem/sub.h [new file with mode: 0644]
sel/Makefile.am [new file with mode: 0644]
sel/bres-adns.c [new file with mode: 0644]
sel/bres.3 [new file with mode: 0644]
sel/bres.c [new file with mode: 0644]
sel/bres.h [new file with mode: 0644]
sel/conn.3 [new file with mode: 0644]
sel/conn.c [new file with mode: 0644]
sel/conn.h [new file with mode: 0644]
sel/ident.3 [new file with mode: 0644]
sel/ident.c [new file with mode: 0644]
sel/ident.h [new file with mode: 0644]
sel/sel.3 [new file with mode: 0644]
sel/sel.c [new file with mode: 0644]
sel/sel.h [new file with mode: 0644]
sel/selbuf.3 [new file with mode: 0644]
sel/selbuf.c [new file with mode: 0644]
sel/selbuf.h [new file with mode: 0644]
sel/selpk.3 [new file with mode: 0644]
sel/selpk.c [new file with mode: 0644]
sel/selpk.h [new file with mode: 0644]
sel/sig.3 [new file with mode: 0644]
sel/sig.c [new file with mode: 0644]
sel/sig.h [new file with mode: 0644]
struct/Makefile.am [new file with mode: 0644]
struct/assoc.3 [new file with mode: 0644]
struct/assoc.c [new file with mode: 0644]
struct/assoc.h [new file with mode: 0644]
struct/atom.3 [new file with mode: 0644]
struct/atom.c [new file with mode: 0644]
struct/atom.h [new file with mode: 0644]
struct/buf-dstr.c [new file with mode: 0644]
struct/buf.3 [new file with mode: 0644]
struct/buf.c [new file with mode: 0644]
struct/buf.h [new file with mode: 0644]
struct/darray.3 [new file with mode: 0644]
struct/darray.c [new file with mode: 0644]
struct/darray.h [new file with mode: 0644]
struct/dspool.3 [new file with mode: 0644]
struct/dspool.c [new file with mode: 0644]
struct/dspool.h [new file with mode: 0644]
struct/dstr-putf.c [new file with mode: 0644]
struct/dstr.3 [new file with mode: 0644]
struct/dstr.c [new file with mode: 0644]
struct/dstr.h [new file with mode: 0644]
struct/hash.3 [new file with mode: 0644]
struct/hash.c [new file with mode: 0644]
struct/hash.h [new file with mode: 0644]
struct/sym.3 [new file with mode: 0644]
struct/sym.c [new file with mode: 0644]
struct/sym.h [new file with mode: 0644]
struct/t/assoc-test.c [new file with mode: 0644]
struct/t/da-gtest.py [new file with mode: 0644]
struct/t/da-test.c [new file with mode: 0644]
struct/t/dstr-putf-test.c [new file with mode: 0644]
struct/t/sym-gtest.py [new file with mode: 0644]
struct/t/sym-test.c [new file with mode: 0644]
struct/tests.at [new file with mode: 0644]
sys/Makefile.am [new file with mode: 0644]
sys/daemonize.3 [new file with mode: 0644]
sys/daemonize.c [new file with mode: 0644]
sys/daemonize.h [new file with mode: 0644]
sys/env.3 [new file with mode: 0644]
sys/env.c [new file with mode: 0644]
sys/env.h [new file with mode: 0644]
sys/fdflags.3 [new file with mode: 0644]
sys/fdflags.c [new file with mode: 0644]
sys/fdflags.h [new file with mode: 0644]
sys/fdpass.3 [new file with mode: 0644]
sys/fdpass.c [new file with mode: 0644]
sys/fdpass.h [new file with mode: 0644]
sys/fwatch.3 [new file with mode: 0644]
sys/fwatch.c [new file with mode: 0644]
sys/fwatch.h [new file with mode: 0644]
sys/lock.3 [new file with mode: 0644]
sys/lock.c [new file with mode: 0644]
sys/lock.h [new file with mode: 0644]
sys/mdup.3 [new file with mode: 0644]
sys/mdup.c [new file with mode: 0644]
sys/mdup.h [new file with mode: 0644]
sys/t/fdpass-test.c [new file with mode: 0644]
sys/t/mdup-test.c [new file with mode: 0644]
sys/tests.at [new file with mode: 0644]
sys/tv.3 [new file with mode: 0644]
sys/tv.c [new file with mode: 0644]
sys/tv.h [new file with mode: 0644]
t/Makefile.am [new file with mode: 0644]
t/atlocal.in [new file with mode: 0644]
test/Makefile.am [new file with mode: 0644]
test/testrig.3 [new file with mode: 0644]
test/testrig.c [new file with mode: 0644]
test/testrig.h [new file with mode: 0644]
trace/Makefile.am [new file with mode: 0644]
trace/trace.3 [new file with mode: 0644]
trace/trace.c [new file with mode: 0644]
trace/trace.h [new file with mode: 0644]
trace/traceopt.c [new file with mode: 0644]
ui/Makefile.am [new file with mode: 0644]
ui/mdwopt.3 [new file with mode: 0644]
ui/pquis.c [new file with mode: 0644]
ui/quis.3 [new file with mode: 0644]
ui/quis.c [new file with mode: 0644]
ui/quis.h [new file with mode: 0644]
ui/report.3 [new file with mode: 0644]
ui/report.c [new file with mode: 0644]
ui/report.h [new file with mode: 0644]
utils/Makefile.am [new file with mode: 0644]
utils/align.3 [new file with mode: 0644]
utils/align.h [new file with mode: 0644]
utils/bits.3 [new file with mode: 0644]
utils/bits.h [new file with mode: 0644]
utils/compiler.3 [new file with mode: 0644]
utils/compiler.h [new file with mode: 0644]
utils/exc.3 [new file with mode: 0644]
utils/exc.c [new file with mode: 0644]
utils/exc.h [new file with mode: 0644]
utils/macros.3 [new file with mode: 0644]
utils/macros.h [new file with mode: 0644]
utils/str.3 [new file with mode: 0644]
utils/str.c [new file with mode: 0644]
utils/str.h [new file with mode: 0644]
utils/t/bits-test.c [new file with mode: 0644]
utils/t/bits-testgen.py [new file with mode: 0644]
utils/t/exc-test.c [new file with mode: 0644]
utils/t/versioncmp-test.c [new file with mode: 0644]
utils/t/versioncmp.tests [new file with mode: 0644]
utils/tests.at [new file with mode: 0644]
utils/versioncmp.3 [new file with mode: 0644]
utils/versioncmp.c [new file with mode: 0644]
utils/versioncmp.h [new file with mode: 0644]
vars.am [new file with mode: 0644]

diff --git a/.ext/cfd/.skelrc b/.ext/cfd/.skelrc
new file mode 100644 (file)
index 0000000..3b4e199
--- /dev/null
@@ -0,0 +1,9 @@
+;;; -*-emacs-lisp-*-
+
+(setq skel-alist
+      (append
+       '((author . "Mark Wooding")
+        (full-title . "the Common Files Distribution (`common')")
+        (Program . "`Common'")
+        (program . "`common'"))
+       skel-alist))
similarity index 100%
rename from build/autotest.am
rename to .ext/cfd/build/autotest.am
similarity index 100%
rename from build/confsubst
rename to .ext/cfd/build/confsubst
similarity index 100%
rename from build/maninst
rename to .ext/cfd/build/maninst
similarity index 100%
rename from build/mdwsetup.py
rename to .ext/cfd/build/mdwsetup.py
similarity index 100%
rename from build/pysetup.mk
rename to .ext/cfd/build/pysetup.mk
similarity index 100%
rename from doc/INSTALL
rename to .ext/cfd/doc/INSTALL
similarity index 100%
rename from doc/texinice.tex
rename to .ext/cfd/doc/texinice.tex
similarity index 100%
rename from licence/AGPL-3
rename to .ext/cfd/licence/AGPL-3
similarity index 100%
rename from licence/GPL-1
rename to .ext/cfd/licence/GPL-1
similarity index 100%
rename from licence/GPL-2
rename to .ext/cfd/licence/GPL-2
similarity index 100%
rename from licence/GPL-3
rename to .ext/cfd/licence/GPL-3
similarity index 100%
rename from licence/LGPL-2
rename to .ext/cfd/licence/LGPL-2
similarity index 100%
rename from licence/LGPL-2.1
rename to .ext/cfd/licence/LGPL-2.1
similarity index 100%
rename from licence/LGPL-3
rename to .ext/cfd/licence/LGPL-3
similarity index 100%
rename from licence/gpl.texi
rename to .ext/cfd/licence/gpl.texi
similarity index 100%
rename from m4/mdw-manext.m4
rename to .ext/cfd/m4/mdw-manext.m4
similarity index 100%
rename from src/getdate.h
rename to .ext/cfd/src/getdate.h
similarity index 100%
rename from src/getdate.y
rename to .ext/cfd/src/getdate.y
similarity index 100%
rename from src/mdwopt.c
rename to .ext/cfd/src/mdwopt.c
similarity index 100%
rename from src/mdwopt.h
rename to .ext/cfd/src/mdwopt.h
diff --git a/.gitignore b/.gitignore
new file mode 100644 (file)
index 0000000..32d2ec1
--- /dev/null
@@ -0,0 +1,24 @@
+## Pervasive build machinery.
+Makefile.in
+
+## Common source files.
+/ui/mdwopt.c
+/ui/mdwopt.h
+
+## Other common files.
+/COPYING.LIB
+/COPYING
+
+## Top-level generated files.
+/aclocal.m4
+/autom4te.cache/
+/configure
+/config/
+/precomp/
+
+## Test machinery.
+/t/autotest.am
+/t/package.m4
+/t/testsuite.at
+/t/testsuite
+/t/tests.m4
diff --git a/.links b/.links
new file mode 100644 (file)
index 0000000..e342330
--- /dev/null
+++ b/.links
@@ -0,0 +1,9 @@
+config/maninst
+config/auto-version
+config/confsubst
+ui/mdwopt.c
+ui/mdwopt.h
+t/autotest.am
+t/testsuite.at
+COPYING
+COPYING.LIB
diff --git a/.mailmap b/.mailmap
new file mode 100644 (file)
index 0000000..96fe7ad
--- /dev/null
+++ b/.mailmap
@@ -0,0 +1 @@
+Mark Wooding <mdw@distorted.org.uk> <mdw>
diff --git a/.skelrc b/.skelrc
index 3b4e199..adff5ec 100644 (file)
--- a/.skelrc
+++ b/.skelrc
@@ -2,8 +2,8 @@
 
 (setq skel-alist
       (append
-       '((author . "Mark Wooding")
-        (full-title . "the Common Files Distribution (`common')")
-        (Program . "`Common'")
-        (program . "`common'"))
+       '((author . "Straylight/Edgeware")
+        (full-title . "the mLib utilities library")
+        (library . "mLib")
+        (licence-text . "[[lgpl-2]]"))
        skel-alist))
diff --git a/Makefile.am b/Makefile.am
new file mode 100644 (file)
index 0000000..48f2317
--- /dev/null
@@ -0,0 +1,149 @@
+### -*-makefile-*-
+###
+### Top-level build for mLib
+###
+### (c) 2009 Straylight/Edgeware
+###
+
+###----- Licensing notice ---------------------------------------------------
+###
+### This file is part of the mLib utilities library.
+###
+### mLib is free software; you can redistribute it and/or modify
+### it under the terms of the GNU Library General Public License as
+### published by the Free Software Foundation; either version 2 of the
+### License, or (at your option) any later version.
+###
+### mLib is distributed in the hope that it will be useful,
+### but WITHOUT ANY WARRANTY; without even the implied warranty of
+### MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+### GNU Library General Public License for more details.
+###
+### You should have received a copy of the GNU Library General Public
+### License along with mLib; if not, write to the Free
+### Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+### MA 02111-1307, USA.
+
+include $(top_srcdir)/vars.am
+
+SUBDIRS                         =
+
+###--------------------------------------------------------------------------
+### Top-level library.
+
+lib_LTLIBRARIES                 = libmLib.la
+libmLib_la_LDFLAGS      = -version-info $(LIBTOOL_VERSION_INFO) \
+                               -no-undefined
+libmLib_la_SOURCES      =
+libmLib_la_LIBADD       = $(MLIB_LIBS)
+
+###--------------------------------------------------------------------------
+### Package-configuration file.
+
+pkgconfigdir            = $(libdir)/pkgconfig
+pkgconfig_DATA          = mLib.pc
+EXTRA_DIST             += mLib.pc.in
+CLEANFILES             += mLib.pc
+
+mLib.pc: mLib.pc.in Makefile
+       $(SUBST) $(srcdir)/mLib.pc.in >$@.new \
+               $(SUBSTITUTIONS) && \
+               mv $@.new $@
+
+###--------------------------------------------------------------------------
+### Subdirectories.
+###
+### Note: There are implicit dependencies between the subdirectories.  Be
+### careful about reordering them.
+
+## Utilities.
+SUBDIRS                        += utils
+libmLib_la_LIBADD      += utils/libutils.la
+
+## Memory allocation.
+SUBDIRS                        += mem
+libmLib_la_LIBADD      += mem/libmem.la
+
+## User interface.
+SUBDIRS                        += ui
+libmLib_la_LIBADD      += ui/libui.la
+
+## Hashing.
+SUBDIRS                        += hash
+libmLib_la_LIBADD      += hash/libhash.la
+
+## Data structures.
+SUBDIRS                        += struct
+libmLib_la_LIBADD      += struct/libstruct.la
+
+## Encoding and decoding.
+SUBDIRS                        += codec
+libmLib_la_LIBADD      += codec/libcodec.la
+
+## System utilities.
+SUBDIRS                        += sys
+libmLib_la_LIBADD      += sys/libsys.la
+
+## Buffering.
+SUBDIRS                        += buf
+libmLib_la_LIBADD      += buf/libbuf.la
+
+## Event-driven networking.
+SUBDIRS                        += sel
+libmLib_la_LIBADD      += sel/libsel.la
+
+## Testing.
+SUBDIRS                        += test
+libmLib_la_LIBADD      += test/libtest.la
+
+## Tracing.
+SUBDIRS                        += trace
+libmLib_la_LIBADD      += trace/libtrace.la
+
+###--------------------------------------------------------------------------
+### Testing.
+
+SUBDIRS                        += t
+
+###--------------------------------------------------------------------------
+### Distribution.
+
+## Make sure the precomputed tables are available.  Hang this off of any
+## distributed file.
+mLib.pc.in: ensure-precomp-libs
+ensure-precomp-libs:
+       for d in ui utils; do (cd $$d && $(MAKE) all) || exit 1; done
+
+## Release number.
+dist-hook::
+       echo $(VERSION) >$(distdir)/RELEASE
+
+## Additional build tools.
+EXTRA_DIST             += config/confsubst
+EXTRA_DIST             += config/auto-version
+EXTRA_DIST             += config/maninst
+
+###--------------------------------------------------------------------------
+### Debian.
+
+## General stuff.
+EXTRA_DIST             += debian/rules debian/copyright
+EXTRA_DIST             += debian/control debian/changelog
+EXTRA_DIST             += debian/compat debian/source/format
+EXTRA_DIST             += debian/common.symbols
+
+## mlib2
+EXTRA_DIST             += debian/mlib2.install
+EXTRA_DIST             += debian/mlib2.symbols
+
+## mlib2-adns
+EXTRA_DIST             += debian/mlib2-adns.install.in
+EXTRA_DIST             += debian/mlib2-adns.symbols
+
+## mlib-bin
+EXTRA_DIST             += debian/mlib-bin.install
+
+## mlib-dev
+EXTRA_DIST             += debian/mlib-dev.install
+
+###----- That's all, folks --------------------------------------------------
diff --git a/README b/README
new file mode 100644 (file)
index 0000000..e64fd10
--- /dev/null
+++ b/README
@@ -0,0 +1,197 @@
+mLib
+
+
+Overview
+
+       mLib is a collection of bits of code I've found handy in various
+       programs.  Once upon a time, almost all of the code was nearly-
+       strictly conforming C.  Since then, a big pile of Unix-specific
+       code's been added, with a particularly strong networking
+       emphasis.  The Unix-specific code is targetted at sensible
+       POSIX-conforming systems, and should port easily.
+
+       My programming style is probably what you'd describe as
+       `unconventional'.  I'm skeptical about object-orientation, and
+       not a huge fan of over-hiding information of any kind.  The
+       structure and interface of the library reflects these views.  On
+       the other hand, I like documentation quite a lot.
+
+
+Documentation
+
+       There is now a (hopefully fairly good) set of manual pages for
+       mLib.  The manual isn't installed by default since it takes a
+       while to install and it's not a very good idea when it's part
+       of a larger package.  To install the manual pages, say
+
+               make install-man
+
+       (after everything else is built, obviously).
+
+       There's also documentation in the header files.  The header file
+       comments were, in general, written at the same time as the code.
+       This has the disadvantage that they focus at a fairly low level,
+       and use terminology relating to the implementation rather than
+       the interface.
+
+       The header file comments can be handy for quick browsing.
+       However, the manual pages are considered the authoritative
+       source of information about the programming interface.  If you
+       have to look at the source code, it usually means that my
+       documentation or interface design is wrong.
+
+
+Quick tour
+
+       mLib doesn't have much of a `structure' as such.  It's more a
+       collection of useful things than a coherent whole.  Even so,
+       it's possible to detect a vague layering of the library's
+       components.
+
+       The underpinnings of the system are the exception structure, and
+       the memory allocation routines.
+
+         * `exc.h' declares some macros which do a reasonable (though
+           not perfect) job of providing exception handling facilities
+           for C.
+
+         * `alloc.h' declares some thin veneers over `malloc' and
+           friends which raise exceptions for out-of-memory conditions,
+           so you don't have to bother trapping these in main code.
+
+       Above this are the memory tracking system, the suballocator, and
+       a couple of useful data structures.
+
+         * `track.h' declares the symbols required for a (very) simple
+           memory allocation tracker.  I wrote it because I had a
+           memory leak in a program.  It tags allocated memory blocks
+           with information about who allocated them, and keeps track
+           of the memory allocated so far.  Most of the time, you don't
+           bother compiling this in, and you don't need to care about
+           it at all.  [This may be withdrawn in a later release.  Too
+           much code in mLib doesn't support it properly, and it's not
+           being maintained or documented very well.]
+
+         * `sub.h' provides an allocation mechanism for small,
+           known-size blocks.  It fetches big chunks from an underlying
+           allocator and divvies them up into small ones.  This reduces
+           the overhead of calling the underlying allocator, and
+           (because the small blocks are linked together in lists by
+           their size) means that allocation and freeing are very
+           quick.  It also reduces the amount of memory used by a small
+           amount.
+
+         * `sym.h' provides a symbol table manager.  It uses an
+           extensible hashing scheme (with 32-bit CRC as the hash
+           function), and permits arbitrary data blocks in both the
+           keys and values.  It seems fairly quick.
+
+         * `hash.h' provides the underpinnings of the `sym' hashtable.
+           It's a completely generic hashtable skeleton.  It provides
+           the basics like rehashing when the table is full and so on.
+           It needs a fair bit of work to turn into a usable data
+           structure, though, which is what `sym' is for.
+
+         * `dstr.h' provides a dynamically sized string type.  In the
+           rather paltry tests I've run, it seemed faster than
+           libstdc++'s string type, but I shouldn't read too much into
+           that if I were you.  The representation is exposed, in case
+           you want to start fiddling with it.  Just make sure that the
+           string looks sane before you start expecting any of the
+           functions or macros to work.
+
+         * `darray.h' implements dynamically growing arrays which can be
+           extended at both ends, a bit like Perl's.  It replaces the
+           old `dynarray.h' which wasn't very good.  However, `darray's
+           arrays are not sparse.  Maybe a good sparse array module
+           will be added later.
+
+       At the same conceptual level, there's some code for handling
+       multiplexed I/O.  Although the core is named `sel', and it uses
+       `select' internally, it could fairly easily be changed to use
+       `poll' instead.
+
+         * `tv.h' declares some useful arithmetic operations on the
+           `struct timeval' type used by the `select' system call.
+
+         * `sel.h' declares a collection of types and routines for
+           handling `select' calls in a nice way.  There's nothing
+           particularly exciting in itself, but it's a good base on
+           which to build other things.
+
+         * `lbuf.h' accepts arbitrary chunks of data and then passes
+           completed text lines on to a function.  This is handy when
+           you're trying to read text from a socket, but don't want to
+           block while the end of the line is on its way.  (In
+           particular, that'd leave you vulnerable to a trivial denial-
+           of-service attack.)
+
+         * `selbuf.h' implements an `lbuf' tied to a read selector.
+           Whenever completed lines are read from a particular source,
+           they're passed to a handler function.
+
+         * `conn.h' handles non-blocking `connect'.  It starts a
+           connect, and adds itself to the select system.  When the
+           connect completes, you get given the file descriptor.
+
+         * `ident.h' is an RFC931 (identd) client.
+
+         * `bres.h' is a background name resolver.  It keeps a pool of
+           resolver processes to answer queries.
+
+         * `sig.h' traps signals and dispatches them through the
+           event-driven `sel' system.
+
+       Then there's a bunch of other stuff.
+
+         * `base32.h' does Base32 encoding and decoding.    This is a
+           mad thing one needs for sha1 URNs.
+
+         * `base64.h' does Base64 encoding and decoding.
+
+         * `bits.h' provides some portable bit manipulation macros.
+
+         * `crc32.h' declares the 32-bit CRC used by `sym'.
+
+         * `env.h' provides some routines for handling environment
+           variables in a hashtable.
+
+         * `fdflags.h' encapsulates some traditional little dances with
+           fcntl when playing with nonblocking files.
+
+         * `hex.h' does hex encoding and decoding.
+
+         * `lock.h' does fcntl-style locking with a timeout.
+
+         * `quis.h' works out the program name from the value of
+           `argv[0]' and puts it in a variable for everything else to
+           find.
+
+         * `report.h' reports fatal and nonfatal errors in the standard
+           Unixy way.
+
+         * `str.h' provides some occasionally useful string-
+           manipulation toys.
+
+         * `trace.h' provides a simple tracing facility, which can be
+           turned on and off both at compile- and run-time.
+
+         * `testrig.h' is a generic test rig skeleton.  It reads test
+           vector files with a slightly configurable syntax, passes the
+           arguments to a caller-defined test function, and reports the
+           results.  It's particularly handy with cryptographic
+           algorithms, I find.
+
+         * `unihash.h' provides universal hashing.  This is useful in
+           hash tables for preventing uneven loading even in the
+           presence of a malicious person choosing the hash keys.
+
+         * `url.h' does url-encoding, which armours mostly-textual
+           name/value pairs so they contain no whitespace characters.
+
+-- [mdw]
+
+\f
+Local variables:
+mode: text
+End:
diff --git a/buf/Makefile.am b/buf/Makefile.am
new file mode 100644 (file)
index 0000000..5c722fb
--- /dev/null
@@ -0,0 +1,45 @@
+### -*-makefile-*-
+###
+### Build script for buffering
+###
+### (c) 2009 Straylight/Edgeware
+###
+
+###----- Licensing notice ---------------------------------------------------
+###
+### This file is part of the mLib utilities library.
+###
+### mLib is free software; you can redistribute it and/or modify
+### it under the terms of the GNU Library General Public License as
+### published by the Free Software Foundation; either version 2 of the
+### License, or (at your option) any later version.
+###
+### mLib is distributed in the hope that it will be useful,
+### but WITHOUT ANY WARRANTY; without even the implied warranty of
+### MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+### GNU Library General Public License for more details.
+###
+### You should have received a copy of the GNU Library General Public
+### License along with mLib; if not, write to the Free
+### Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+### MA 02111-1307, USA.
+
+include $(top_srcdir)/vars.am
+
+noinst_LTLIBRARIES      = libbuf.la
+libbuf_la_SOURCES       =
+
+###--------------------------------------------------------------------------
+### Component files.
+
+## Line buffering.
+pkginclude_HEADERS     += lbuf.h
+libbuf_la_SOURCES      += lbuf.c
+LIBMANS                        += lbuf.3
+
+## Packet buffering.
+pkginclude_HEADERS     += pkbuf.h
+libbuf_la_SOURCES      += pkbuf.c
+LIBMANS                        += pkbuf.3
+
+###----- That's all, folks --------------------------------------------------
diff --git a/buf/lbuf.3 b/buf/lbuf.3
new file mode 100644 (file)
index 0000000..6595f30
--- /dev/null
@@ -0,0 +1,240 @@
+.\" -*-nroff-*-
+.TH lbuf 3 "6 July 1999" "Straylight/Edgeware" "mLib utilities library"
+.SH "NAME"
+lbuf \- split lines out of asynchronously received blocks
+.\" @lbuf_flush
+.\" @lbuf_close
+.\" @lbuf_free
+.\" @lbuf_snarf
+.\" @lbuf_setsize
+.\" @lbuf_init
+.\" @lbuf_destroy
+.SH "SYNOPSIS"
+.nf
+.B "#include <mLib/lbuf.h>"
+
+.BI "void lbuf_flush(lbuf *" b ", char *" p ", size_t " len );
+.BI "void lbuf_close(lbuf *" b );
+.BI "size_t lbuf_free(lbuf *" b ", char **" p );
+.BI "void lbuf_snarf(lbuf *" b ", const void *" p ", size_t " sz );
+.BI "void lbuf_setsize(lbuf *" b ", size_t " sz );
+.BI "void lbuf_init(lbuf *" b ", lbuf_func *" func ", void *" p );
+.BI "void lbuf_destroy(lbuf *" b );
+.fi
+.SH "DESCRIPTION"
+The declarations in
+.B <mLib/lbuf.h>
+implement a handy object called a
+.IR "line buffer" .
+Given unpredictably-sized chunks of data, the line buffer extracts
+completed lines of text and passes them to a caller-supplied function.
+This is useful in nonblocking network servers, for example: the server
+can feed input from a client into a line buffer as it arrives and deal
+with completed text lines as they appear without having to wait for
+newline characters.
+.PP
+The state of a line buffer is stored in an object of type
+.BR lbuf .
+This is a structure which must be allocated by the caller.  The
+structure should normally be considered opaque (see the section on
+.B Disablement
+for an exception to this).
+.SS "Initialization and finalization"
+The function
+.B lbuf_init
+initializes a line buffer ready for use.  It is given three arguments:
+.TP
+.BI "lbuf *" b
+A pointer to the block of memory to use for the line buffer.  The line
+buffer will allocate memory to store incoming data automatically: this
+structure just contains bookkeeping information.
+.TP
+.BI "lbuf_func *" func
+The
+.I line-handler
+function to which the line buffer should pass completed lines of text.
+See
+.B "Line-handler functions"
+below for a description of this function.
+.TP
+.BI "void *" p
+A pointer argument to be passed to the function when a completed line of
+text arrives.
+.PP
+The amount of memory set aside for reading lines is configurable.  It
+may be set by calling
+.B lbuf_setsize
+at any time when the buffer is empty.  The default limit is 256 bytes.
+Lines longer than the limit are truncated.  By default, the buffer is
+allocated from the current arena,
+.BR arena_global (3);
+this may be changed by altering the buffer's
+.B a
+member to refer to a different arena at any time when the buffer is
+unallocated.
+.PP
+A line buffer must be destroyed after use by calling
+.BR lbuf_destroy ,
+passing it the address of the buffer block.
+.SS "Inserting data into the buffer"
+There are two interfaces for inserting data into the buffer.  One's much
+simpler than the other, although it's less expressive.
+.PP
+The simple interface is
+.BR lbuf_snarf .
+This function is given three arguments: a pointer
+.I b
+to a line buffer structure; a pointer
+.I p
+to a chunk of data to read; and the size
+.I sz
+of the chunk of data.  The data is pushed through the line buffer and
+any complete lines are passed on to the line handler.
+.PP
+The complex interface is the pair of functions
+.B lbuf_free
+and
+.BR lbuf_flush .
+.PP
+The
+.B lbuf_free
+function returns the address and size of a free portion of the line
+buffer's memory into which data may be written.  The function is passed
+the address
+.I b
+of the line buffer.  Its result is the size of the free area, and it
+writes the base address of this free space to the location pointed to by
+the argument
+.IR p .
+The caller's data must be written to ascending memory locations starting
+at
+.BI * p
+and no data may be written beyond the end of the free space.  However,
+it isn't necessary to completely fill the buffer.
+.PP
+Once the free area has had some data written to it,
+.B lbuf_flush
+is called to examine the new data and break it into text lines.  This is
+given three arguments:
+.TP
+.BI "lbuf *" b
+The address of the line buffer.
+.TP
+.BI "char *" p
+The address at which the new data has been written.  This must be the
+base address returned from
+.BR lbuf_free .
+.TP
+.BI "size_t " len
+The number of bytes which have been written to the buffer.
+.PP
+The
+.B lbuf_flush
+function breaks the new data into lines as described below, and passes
+each one in turn to the line-handler function.
+.PP
+The
+.B lbuf_snarf
+function is trivially implemented in terms of the more complex
+.BR lbuf_free / lbuf_flush
+interface.
+.SS "Line breaking"
+By default, the line buffer considers a line to end with either a simple
+linefeed character (the normal Unix convention) or a
+carriage-return/linefeed pair (the Internet convention).  This can be
+changed by modifying the
+.B delim
+member of the
+.B lbuf
+structure: the default value is
+.BR LBUF_CRLF .
+If set to
+.BR LBUF_STRICTCRLF ,
+only a carriage-return/linefeed pair will terminate a line.  Any other
+value is a single character which is considered to be the line terminator.
+.PP
+The line buffer has a fixed amount of memory available to it.  This is
+deliberate, to prevent a trivial attack whereby a remote user sends a
+stream of data containing no newline markers, wasting the server's
+memory.  Instead, the buffer will truncate overly long lines (silently)
+and return only the initial portion.  It will ignore the rest of the
+line completely.
+.SS "Line-handler functions"
+Completed lines, as already said, are passed to the caller's
+line-handler function.  This function has the signature
+.IP
+.B "void"
+.IB func "(char *" s ", size_t " len ", void *" p );
+.PP
+It is given three arguments: the address
+.I s
+of the line which has just been read; the length
+.I len
+of the line (not including the null terminator), and the pointer
+.I p
+which was set up in the call to
+.BR lbuf_init .
+The line passed is null-terminated, and has had its trailing newline
+stripped.  The area of memory in which the string is located may be
+overwritten by the line-handler function, although writing beyond the
+terminating zero byte is not permitted.
+.PP
+The line pointer argument
+.I s
+may be null to signify end-of-file; in this case, the length
+.I len
+will be zero.  See the next section.
+.SS "Flushing the remaining data"
+When the client program knows that there's no more data arriving (for
+example, an end-of-file condition exists on its data source) it should
+call the function
+.BR lbuf_close
+to flush out the remaining data in the buffer as one last (improperly
+terminated) line.  This will pass the remaining text to the line
+handler, if there is any, and then call the handler one final time with
+a null pointer rather than the address of a text line to inform it of
+the end-of-file.
+.SS "Disablement"
+The line buffer is intended to be used in higher-level program objects,
+such as the buffer selector described in
+.BR selbuf (3).
+Unfortunately, a concept from this high level needs to exist at the line
+buffer level, which complicates the description somewhat.  The idea is
+that, when a line-handler attached to some higher-level object decides
+that it's read enough, it can
+.I disable
+the object so that it doesn't see any more data.
+.PP
+Clearly, since an
+.B lbuf_flush
+call can emit more than one line, it must be aware that the line handler
+isn't interested in any more lines.  However, this fact must also be
+signalled to the higher-level object so that it can detach itself from
+its data source.
+.PP
+Rather than invent some complex interface for this, the line buffer
+exports one of its structure members, a flags word called
+.BR f .
+A higher-level object wishing to disable the line buffer simply clears
+the bit
+.B LBUF_ENABLE
+in this flags word.
+.PP
+Disabling a buffer causes an immediate return from
+.BR lbuf_flush .
+However, it is not permitted for the functions
+.B lbuf_flush
+or
+.B lbuf_close
+to be called on a disabled buffer.  (This condition isn't checked for;
+it'll just do the wrong thing.)  Furthermore, the
+.B lbuf_snarf
+function does not handle disablement at all, because it would complicate
+the interface so much that it wouldn't have any advantage over the more
+general
+.BR lbuf_free / lbuf_flush .
+.SH "SEE ALSO"
+.BR selbuf (3),
+.BR mLib (3).
+.SH "AUTHOR"
+Mark Wooding, <mdw@distorted.org.uk>
diff --git a/buf/lbuf.c b/buf/lbuf.c
new file mode 100644 (file)
index 0000000..0cd391c
--- /dev/null
@@ -0,0 +1,315 @@
+/* -*-c-*-
+ *
+ * Block-to-line buffering
+ *
+ * (c) 1999 Straylight/Edgeware
+ */
+
+/*----- Licensing notice --------------------------------------------------*
+ *
+ * This file is part of the mLib utilities library.
+ *
+ * mLib is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * mLib is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with mLib; if not, write to the Free
+ * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+ * MA 02111-1307, USA.
+ */
+
+/*----- Header files ------------------------------------------------------*/
+
+#include <assert.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "alloc.h"
+#include "arena.h"
+#include "lbuf.h"
+
+/*----- Main code ---------------------------------------------------------*/
+
+/* --- @lbuf_flush@ --- *
+ *
+ * Arguments:  @lbuf *b@ = pointer to buffer block
+ *             @char *p@ = pointer to where to start searching
+ *             @size_t len@ = length of new material added
+ *
+ * Returns:    ---
+ *
+ * Use:                Flushes any complete lines in a line buffer.  New material
+ *             is assumed to have been added starting at @p@.  If @p@ is
+ *             null, then the scan starts at the beginning of the buffer,
+ *             and the size of data already in the buffer is used in place
+ *             of @len@.
+ *
+ *             It is assumed that the buffer is initially enabled.  You
+ *             shouldn't be contributing data to a disabled buffer anyway.
+ *             However, the buffer handler may at some point disable itself,
+ *             and @lbuf_flush@ can cope with this eventuality.  Any pending
+ *             data is left at the start of the buffer and can be flushed
+ *             out by calling @lbuf_flush(b, 0, 0)@ if the buffer is ever
+ *             re-enabled.
+ */
+
+void lbuf_flush(lbuf *b, char *p, size_t len)
+{
+  char *l;                             /* Limit of data in buffer */
+  char *q;                             /* Roving pointer through string */
+  char *base;                          /* Base address of current line */
+  int cr;                              /* Carriage return state */
+
+  if (b->f & LBUF_CLOSE) {
+    b->func(0, 0, b->p);
+    return;
+  }
+
+  /* --- Initialize variables as necessary --- */
+
+  if (!p) {
+    p = b->buf;
+    cr = 0;
+    len = b->len;
+  } else
+    cr = b->f & LBUF_CR;
+
+  l = p + len;
+
+  /* --- Clear @base@ if I'm discarding an overlong line --- */
+
+  if (b->len == b->sz)
+    base = 0;
+  else
+    base = b->buf;
+
+  /* --- Now I march through the string --- */
+
+  for (q = p; q < l; q++) {
+
+    /* --- Quickly discard uninteresting characters --- */
+
+    switch (b->delim) {
+      case LBUF_CRLF:
+      case LBUF_STRICTCRLF:
+       if (*q != '\r' && *q != '\n') {
+         cr = 0;
+         continue;
+       }
+       if (*q == '\r') {
+         cr = 1;
+         continue;
+       }
+       if (!cr && b->delim == LBUF_STRICTCRLF)
+         continue;
+       break;
+      default:
+       if (*q != b->delim)
+         continue;
+    }
+
+    /* --- I have a positive ID on a delimiter --- *
+     *
+     * If I'm interested in this string, report it to my owner.
+     */
+
+    if (base) {
+      len = q - base;
+      if (cr)
+       len--;                          /* Exercise: why is this safe? */
+      base[len] = 0;
+      b->func(base, len, b->p);
+      if (!(b->f & LBUF_ENABLE)) {
+       base = q + 1;
+       break;
+      }
+    }
+    base = q + 1;
+    cr = 0;
+  }
+
+  /* --- Sift through the aftermath --- */
+
+  if (base) {
+    len = l - base;
+    if (len == b->sz) {
+      b->buf[len - 1] = 0;
+      b->func(base, len - 1, b->p);
+    } else if (base != b->buf)
+      memmove(b->buf, base, len);
+    b->len = len;
+    if (cr)
+      b->f |= LBUF_CR;
+    else
+      b->f &= ~LBUF_CR;
+  }
+}
+
+/* --- @lbuf_close@ --- *
+ *
+ * Arguments:  @lbuf *b@ = pointer to buffer block
+ *
+ * Returns:    ---
+ *
+ * Use:                Empties the buffer of any data currently lurking in it, and
+ *             informs the client that this has happened.  It's assumed that
+ *             the buffer is enabled: you shouldn't be reading close events
+ *             on disabled buffers.  The buffer, if allocated, is freed.
+ */
+
+void lbuf_close(lbuf *b)
+{
+  if (b->len && b->len != b->sz) {
+    b->buf[b->len] = 0;
+    b->func(b->buf, b->len, b->p);
+  }
+  if (b->buf) {
+    x_free(b->a, b->buf);
+    b->buf = 0;
+  }
+  b->f |= LBUF_CLOSE;
+  if (b->f & LBUF_ENABLE)
+    b->func(0, 0, b->p);
+}
+
+/* --- @lbuf_free@ --- *
+ *
+ * Arguments:  @lbuf *b@ = pointer to buffer block
+ *             @char **p@ = output pointer to free space
+ *
+ * Returns:    Free buffer size.
+ *
+ * Use:                Returns the free portion of a line buffer.  Data can then be
+ *             written to this portion, and split out into lines by calling
+ *             @lbuf_flush@.  A buffer is allocated if none currently
+ *             exists.
+ */
+
+size_t lbuf_free(lbuf *b, char **p)
+{
+  /* --- There's a special case to consider --- *
+   *
+   * If a line from the file wouldn't fit in the buffer, I truncate it and
+   * return what would fit.  The rest of the line ought to be discarded.
+   * This condition is signalled by @len = b->sz@, and means that the entire
+   * buffer is OK to be trashed.  In other cases, @len@ is the amount of
+   * space currently occupied in the buffer.  This special case is the reason
+   * this routine exists.
+   */
+
+  if (b->len != 0 && b->len != b->sz) {
+    *p = b->buf + b->len;
+    return (b->sz - b->len);
+  } else {
+    if (!b->buf)
+      b->buf = x_alloc(b->a, b->sz);
+    *p = b->buf;
+    return (b->sz);
+  }
+}
+
+/* --- @lbuf_snarf@ --- *
+ *
+ * Arguments:  @lbuf *b@ = pointer to buffer block
+ *             @const void *p@ = pointer to input data buffer
+ *             @size_t sz@ = size of data in input buffer
+ *
+ * Returns:    ---
+ *
+ * Use:                Snarfs the data from the input buffer and spits it out as
+ *             lines.  This interface ignores the complexities of dealing
+ *             with disablement: you should be using @lbuf_free@ to
+ *             contribute data if you want to cope with that.
+ */
+
+void lbuf_snarf(lbuf *b, const void *p, size_t sz)
+{
+  const char *pp = p;
+  while (sz && (b->f & LBUF_ENABLE)) {
+    size_t bsz;
+    char *bp;
+
+    bsz = lbuf_free(b, &bp);
+    if (bsz > sz)
+      bsz = sz;
+    memcpy(bp, pp, bsz);
+    lbuf_flush(b, bp, bsz);
+    pp += bsz;
+    sz -= bsz;
+  }
+}
+
+/* --- @lbuf_setsize@ --- *
+ *
+ * Arguments:  @lbuf *b@ = pointer to buffer block
+ *             @size_t sz@ = requested maximum line size
+ *
+ * Returns:    ---
+ *
+ * Use:                Modifies the size of the buffer associated with the block.
+ *             It is an error to resize a buffer while it contains data.
+ */
+
+void lbuf_setsize(lbuf *b, size_t sz)
+{
+  if (b->buf)
+  assert(((void)"Buffer in use in lbuf_setsize",
+        b->len == 0 || b->len == b->sz));
+  if (b->buf)
+    x_free(b->a, b->buf);
+  b->sz = sz;
+  b->buf = 0;
+}
+
+/* --- @lbuf_init@ --- *
+ *
+ * Arguments:  @lbuf *b@ = pointer to buffer block
+ *             @lbuf_func *func@ = handler function
+ *             @void *p@ = argument pointer for @func@
+ *
+ * Returns:    ---
+ *
+ * Use:                Initializes a line buffer block.  Any recognized lines are
+ *             passed to @func@ for processing.  No buffer is initially
+ *             allocated; this is done when the buffer is actually required
+ *             for the first time.
+ */
+
+void lbuf_init(lbuf *b, lbuf_func *func, void *p)
+{
+  b->func = func;
+  b->p = p;
+  b->len = 0;
+  b->f = LBUF_ENABLE;
+  b->delim = LBUF_CRLF;
+  b->buf = 0;
+  b->a = arena_global;
+  lbuf_setsize(b, 256);
+}
+
+/* --- @lbuf_destroy@ --- *
+ *
+ * Arguments:  @lbuf *b@ = pointer to buffer block
+ *
+ * Returns:    ---
+ *
+ * Use:                Deallocates a line buffer and frees any resources it owned.
+ */
+
+void lbuf_destroy(lbuf *b)
+{
+  if (b->buf) {
+    x_free(b->a, b->buf);
+    b->buf = 0;
+  }
+}
+
+/*----- That's all, folks -------------------------------------------------*/
diff --git a/buf/lbuf.h b/buf/lbuf.h
new file mode 100644 (file)
index 0000000..c18fea0
--- /dev/null
@@ -0,0 +1,231 @@
+/* -*-c-*-
+ *
+ * Block-to-line buffering
+ *
+ * (c) 1999 Straylight/Edgeware
+ */
+
+/*----- Licensing notice --------------------------------------------------*
+ *
+ * This file is part of the mLib utilities library.
+ *
+ * mLib is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * mLib is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with mLib; if not, write to the Free
+ * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+ * MA 02111-1307, USA.
+ */
+
+#ifndef MLIB_LBUF_H
+#define MLIB_LBUF_H
+
+#ifdef __cplusplus
+  extern "C" {
+#endif
+
+/*----- Line buffering ----------------------------------------------------*
+ *
+ * The line buffer accepts as input arbitrary-sized lumps of data and
+ * converts them, by passing them to a client-supplied function, into a
+ * sequence of lines.  It's particularly useful when performing multiplexed
+ * network I/O.  It's not normally acceptable to block while waiting for the
+ * rest of a text line to arrive, for example.  The line buffer stores the
+ * start of the line until the rest of it arrives later.
+ *
+ * A line is a piece of text terminated by either a linefeed or a carriage-
+ * return/linefeed pair.  (The former is there to cope with Unix; the latter
+ * copes with Internet-format line ends.)
+ *
+ * There's a limit to the size of lines that the buffer can cope with.  It's
+ * not hard to remove this limit, but it's probably a bad idea in a lot of
+ * cases, because it'd allow a remote user to gobble arbitrary amounts of
+ * your memory.  If a line exceeds the limit, it is truncated: the initial
+ * portion of the line is processed normally, and the remaining portion is
+ * simply discarded.
+ *
+ * Lines extracted from the input data are passed, one at a time, to a
+ * `handler function', along with a caller-supplied pointer argument to
+ * provide the handler with some context.  The line read is null-terminated
+ * and does not include the trailing newline characters.  It is legal for a
+ * handler function to modify the string it is passed.  However, writing
+ * beyond the terminating null byte is not allowed.  An end-of-file condition
+ * is signalled to the handler by passing it a null pointer rather than the
+ * address of a string.
+ *
+ * A complexity arises because of the concept of a `disabled' buffer.
+ * Disablement is really a higher-level concept, but it turns out to be
+ * important to implement it here.  It's useful for a line handler function
+ * to `disable' itself, so that it doesn't get called any more.  For example,
+ * this might happen if it encouters an error, or when it finishes reading
+ * everything it wanted to read.  The line buffer needs to be `in the loop'
+ * so that it stops attempting to flush any further lines stored in its
+ * buffer towards a handler function which isn't ready to accept them.
+ * Buffers are initially enabled, although higher- level buffering systems
+ * might well disable them immediately for their own purposes.
+ */
+
+/*----- Header files ------------------------------------------------------*/
+
+#include <stddef.h>
+
+#ifndef MLIB_ARENA_H
+#  include "arena.h"
+#endif
+
+/*----- Data structures ---------------------------------------------------*/
+
+/* --- The buffer structure --- *
+ *
+ * The only thing that's safe to fiddle with in here is the @lbuf_enable@
+ * flag.  Only higher-level buffering systems should be playing with even
+ * that.
+ */
+
+struct lbuf;
+
+typedef void lbuf_func(char */*s*/, size_t /*len*/, void */*p*/);
+
+typedef struct lbuf {
+  lbuf_func *func;                     /* Handler function */
+  void *p;                             /* Argument for handler */
+  size_t len;                          /* Length of data in buffer */
+  size_t sz;                           /* Buffer size */
+  unsigned delim;                      /* Delimiter to look for */
+  unsigned f;                          /* Various useful state flags */
+  arena *a;                            /* Memory allocation arena */
+  char *buf;                           /* The actual buffer */
+} lbuf;
+
+#define LBUF_CR 1u                     /* Read a carriage return */
+#define LBUF_ENABLE 2u                 /* Buffer is currently enabled */
+#define LBUF_CLOSE 4u                  /* Buffer is now closed */
+
+enum {
+  LBUF_CRLF = 256,
+  LBUF_STRICTCRLF = 257
+};
+
+/*----- Functions provided ------------------------------------------------*/
+
+/* --- @lbuf_flush@ --- *
+ *
+ * Arguments:  @lbuf *b@ = pointer to buffer block
+ *             @char *p@ = pointer to where to start searching
+ *             @size_t len@ = length of new material added
+ *
+ * Returns:    ---
+ *
+ * Use:                Flushes any complete lines in a line buffer.  New material
+ *             is assumed to have been added starting at @p@.  If @p@ is
+ *             null, then the scan starts at the beginning of the buffer,
+ *             and the size of data already in the buffer is used in place
+ *             of @len@.
+ *
+ *             It is assumed that the buffer is initially enabled.  You
+ *             shouldn't be contributing data to a disabled buffer anyway.
+ *             However, the buffer handler may at some point disable itself,
+ *             and @lbuf_flush@ can cope with this eventuality.  Any pending
+ *             data is left at the start of the buffer and can be flushed
+ *             out by calling @lbuf_flush(b, 0, 0)@ if the buffer is ever
+ *             re-enabled.
+ */
+
+extern void lbuf_flush(lbuf */*b*/, char */*p*/, size_t /*len*/);
+
+/* --- @lbuf_close@ --- *
+ *
+ * Arguments:  @lbuf *b@ = pointer to buffer block
+ *
+ * Returns:    ---
+ *
+ * Use:                Empties the buffer of any data currently lurking in it, and
+ *             informs the client that this has happened.  It's assumed that
+ *             the buffer is enabled: you shouldn't be reading close events
+ *             on disabled buffers.
+ */
+
+extern void lbuf_close(lbuf */*b*/);
+
+/* --- @lbuf_free@ --- *
+ *
+ * Arguments:  @lbuf *b@ = pointer to buffer block
+ *             @char **p@ = output pointer to free space
+ *
+ * Returns:    Free buffer size.
+ *
+ * Use:                Returns the free portion of a line buffer.  Data can then be
+ *             written to this portion, and split out into lines by calling
+ *             @lbuf_flush@.
+ */
+
+extern size_t lbuf_free(lbuf */*b*/, char **/*p*/);
+
+/* --- @lbuf_snarf@ --- *
+ *
+ * Arguments:  @lbuf *b@ = pointer to buffer block
+ *             @const void *p@ = pointer to input data buffer
+ *             @size_t sz@ = size of data in input buffer
+ *
+ * Returns:    ---
+ *
+ * Use:                Snarfs the data from the input buffer and spits it out as
+ *             lines.  This interface ignores the complexities of dealing
+ *             with disablement: you should be using @lbuf_free@ to
+ *             contribute data if you want to cope with that.
+ */
+
+extern void lbuf_snarf(lbuf */*b*/, const void */*p*/, size_t /*sz*/);
+
+/* --- @lbuf_setsize@ --- *
+ *
+ * Arguments:  @lbuf *b@ = pointer to buffer block
+ *             @size_t sz@ = requested maximum line size
+ *
+ * Returns:    ---
+ *
+ * Use:                Allocates a buffer of the requested size reading lines.
+ */
+
+extern void lbuf_setsize(lbuf */*b*/, size_t /*sz*/);
+
+/* --- @lbuf_init@ --- *
+ *
+ * Arguments:  @lbuf *b@ = pointer to buffer block
+ *             @lbuf_func *func@ = handler function
+ *             @void *p@ = argument pointer for @func@
+ *
+ * Returns:    ---
+ *
+ * Use:                Initializes a line buffer block.  Any recognized lines are
+ *             passed to @func@ for processing.
+ */
+
+extern void lbuf_init(lbuf */*b*/, lbuf_func */*func*/, void */*p*/);
+
+/* --- @lbuf_destroy@ --- *
+ *
+ * Arguments:  @lbuf *b@ = pointer to buffer block
+ *
+ * Returns:    ---
+ *
+ * Use:                Deallocates a line buffer and frees any resources it owned.
+ */
+
+extern void lbuf_destroy(lbuf */*b*/);
+
+/*----- That's all, folks -------------------------------------------------*/
+
+#ifdef __cplusplus
+  }
+#endif
+
+#endif
diff --git a/buf/pkbuf.3 b/buf/pkbuf.3
new file mode 100644 (file)
index 0000000..80eed4b
--- /dev/null
@@ -0,0 +1,225 @@
+.\" -*-nroff-*-
+.TH pkbuf 3 "16 July 2000" "Straylight/Edgeware" "mLib utilities library"
+.SH "NAME"
+pkbuf \- split packets out of asynchronously received blocks
+.\" @pkbuf_flush
+.\" @pkbuf_close
+.\" @pkbuf_free
+.\" @pkbuf_snarf
+.\" @pkbuf_want
+.\" @pkbuf_init
+.\" @pkbuf_destroy
+.SH "SYNOPSIS"
+.nf
+.B "#include <mLib/pkbuf.h>"
+
+.BI "void pkbuf_flush(pkbuf *" pk ", octet *" p ", size_t " len );
+.BI "void pkbuf_close(pkbuf *" pk );
+.BI "size_t pkbuf_free(pkbuf *" pk ", octet **" p );
+.BI "void pkbuf_snarf(pkbuf *" pk ", const void *" p ", size_t " sz );
+.BI "void pkbuf_want(pkbuf *" pk ", size_t " want );
+.BI "void pkbuf_init(pkbuf *" pk ", pkbuf_func *" func ", void *" p );
+.BI "void pkbuf_destroy(pkbuf *" pk );
+.fi
+.SH "DESCRIPTION"
+The declarations in
+.B <mLib/pkbuf.h>
+implement a
+.IR "packet buffer" .
+Given unpredictably-sized chunks of data, the packet buffer extracts
+completed packets of data, with sizes ascertained by a handler
+function.
+.PP
+The state of a packet buffer is stored in an object of type
+.BR pkbuf .
+This is a structure which must be allocated by the caller.  The
+structure should normally be considered opaque (see the section on
+.B Disablement
+for an exception to this).
+.SS "Initialization and finalization"
+The function
+.B pkbuf_init
+initializes a packet buffer ready for use.  It is given three arguments:
+.TP
+.BI "pkbuf *" pk
+A pointer to the block of memory to use for the packet buffer.  The packet
+buffer will allocate memory to store incoming data automatically: this
+structure just contains bookkeeping information.
+.TP
+.BI "pkbuf_func *" func
+The
+.I packet-handler
+function to which the packet buffer should pass packets of data when
+they're received.  See
+.B "Packet breaking and the handler function"
+below.
+.TP
+.BI "void *" p
+A pointer argument to be passed to the function when a packet arrives.
+.PP
+By default, the buffer is
+allocated from the current arena,
+.BR arena_global (3);
+this may be changed by altering the buffer's
+.B a
+member to refer to a different arena at any time when the buffer is
+unallocated.
+.PP
+A packet buffer must be destroyed after use by calling
+.BR pkbuf_destroy ,
+passing it the address of the buffer block.
+.SS "Inserting data into the buffer"
+There are two interfaces for inserting data into the buffer.  One's much
+simpler than the other, although it's less expressive.
+.PP
+The simple interface is
+.BR pkbuf_snarf .
+This function is given three arguments: a pointer
+.I pk
+to a packet buffer structure; a pointer
+.I p
+to a chunk of data to read; and the size
+.I sz
+of the chunk of data.  The data is pushed through the packet buffer and
+any complete packets are passed on to the packet handler.
+.PP
+The complex interface is the pair of functions
+.I pkbuf_free
+and
+.IR pkbuf_flush .
+.PP
+The
+.B pkbuf_free
+function returns the address and size of a free portion of the packet
+buffer's memory into which data may be written.  The function is passed
+the address
+.I pk
+of the packet buffer.  Its result is the size of the free area, and it
+writes the base address of this free space to the location pointed to by
+the argument
+.IR p .
+The caller's data must be written to ascending memory locations starting
+at
+.BI * p
+and no data may be written beyond the end of the free space.  However,
+it isn't necessary to completely fill the buffer.
+.PP
+Once the free area has had some data written to it,
+.B pkbuf_flush
+is called to examine the new data and break it into packets.  This is
+given three arguments:
+.TP
+.BI "pkbuf *" pk
+The address of the packet buffer.
+.TP
+.BI "octet *" p
+The address at which the new data has been written.  This must be the
+base address returned from
+.BR pkbuf_free .
+.TP
+.BI "size_t " len
+The number of bytes which have been written to the buffer.
+.PP
+The
+.B pkbuf_flush
+function breaks the new data into packets as described below, and passes
+each one in turn to the packet-handler function.
+.PP
+The
+.B pkbuf_snarf
+function is trivially implemented in terms of the more complex
+.BR pkbuf_free / pkbuf_flush
+interface.
+.SS "Packet breaking and the handler function"
+The function
+.B pkbuf_want
+is used to inform the packet buffer of the expected length of the next
+packet.  It is given the pointer
+.I pk
+to the packet buffer and a size
+.I sz
+of the packet.
+.PP
+When enough data has arrived, the packet-handler function is called.
+This has the signature
+.IP
+.nf
+.BI "void (*" func ")(octet *" b ", size_t " sz ", pkbuf *" p ,
+.BI "             size_t *" keep ", void *" p );
+.fi
+.PP
+It is passed:
+.TP
+.BI "octet *" b
+A pointer to the packet data in the buffer, or zero to signify
+end-of-file.
+.TP
+.BI "size_t " sz
+The size of the packet, as previously configured via
+.BR pkbuf_want .
+.TP
+.BI "pkbuf *" pk
+A pointer to the packet buffer.
+.TP
+.BI "size_t *" keep
+A location in which to store the number of bytes of the current packet
+to be retained.  The bytes kept are from the end of the current packet:
+if you want to keep bytes from the beginning of the packet, you should
+move them into the right place.
+.TP
+.BI "void *" p
+The pointer which was set up in the call to
+.BR pkbuf_init .
+.PP
+.SS "Flushing the remaining data"
+When the client program knows that there's no more data arriving (for
+example, an end-of-file condition exists on its data source) it should
+call the function
+.BR pkbuf_close .
+This will call the handler one final time with a null pointer to inform
+it of the end-of-file.
+.SS "Disablement"
+The packet buffer is intended to be used in higher-level program
+objects, such as the packet selector described in
+.BR selpk (3).
+Unfortunately, a concept from this high level needs to exist at the
+packet buffer level, which complicates the description somewhat.  The
+idea is that, when a packet-handler attached to some higher-level object
+decides that it's read enough, it can
+.I disable
+the object so that it doesn't see any more data.
+.PP
+Clearly, since an
+.B pkbuf_flush
+call can emit more than one packet, it must be aware that the packet
+handler isn't interested in any more packet.  However, this fact must
+also be signalled to the higher-level object so that it can detach
+itself from its data source.
+.PP
+Rather than invent some complex interface for this, the packet buffer
+exports one of its structure members, a flags words called
+.BR f .
+A higher-level object wishing to disable the packet buffer simply clears
+the bit
+.B PKBUF_ENABLE
+in this flags word.
+.PP
+Disabling a buffer causes an immediate return from
+.BR pkbuf_flush .
+However, it is not permitted for the functions
+.B pkbuf_flush
+or
+.B pkbuf_close
+to be called on a disabled buffer.  (This condition isn't checked for;
+it'll just do the wrong thing.)  Furthermore, the
+.B pkbuf_snarf
+function does not handle disablement at all, because it would complicate
+the interface so much that it wouldn't have any advantage over the more
+general
+.BR pkbuf_free / pkbuf_flush .
+.SH "SEE ALSO"
+.BR lbuf (3),
+.BR selpk (3),
+.BR mLib (3).
+.SH "AUTHOR"
+Mark Wooding, <mdw@distorted.org.uk>
diff --git a/buf/pkbuf.c b/buf/pkbuf.c
new file mode 100644 (file)
index 0000000..6adbecf
--- /dev/null
@@ -0,0 +1,253 @@
+/* -*-c-*-
+ *
+ * Simple packet buffering
+ *
+ * (c) 2000 Straylight/Edgeware
+ */
+
+/*----- Licensing notice --------------------------------------------------*
+ *
+ * This file is part of the mLib utilities library.
+ *
+ * mLib is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * mLib is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with mLib; if not, write to the Free
+ * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+ * MA 02111-1307, USA.
+ */
+
+/*----- Header files ------------------------------------------------------*/
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "alloc.h"
+#include "arena.h"
+#include "pkbuf.h"
+
+/*----- Main code ---------------------------------------------------------*/
+
+/* --- @pkbuf_flush@ --- *
+ *
+ * Arguments:  @pkbuf *pk@ = pointer to buffer block
+ *             @octet *p@ = pointer to where to start searching
+ *             @size_t len@ = length of new material added
+ *
+ * Returns:    ---
+ *
+ * Use:                Flushes any complete packets in a packet buffer.  New
+ *             material is assumed to have been added starting at @p@.  If
+ *             @p@ is null, then the scan starts at the beginning of the
+ *             buffer, and the size of data already in the buffer is used in
+ *             place of @len@.
+ *
+ *             It is assumed that the buffer is initially enabled.  You
+ *             shouldn't be contributing data to a disabled buffer anyway.
+ *             However, the buffer handler may at some point disable itself,
+ *             and @pkbuf_flush@ can cope with this eventuality.  Any
+ *             pending data is left at the start of the buffer and can be
+ *             flushed out by calling @pkbuf_flush(b, 0, 0)@ if the buffer
+ *             is ever re-enabled.
+ */
+
+void pkbuf_flush(pkbuf *pk, octet *p, size_t len)
+{
+  size_t l;
+  size_t o, keep;
+
+  if (pk->f & PKBUF_CLOSE) {
+    pk->func(0, 0, pk, 0, pk->p);
+    return;
+  }
+
+  /* --- Initialize variables as necessary --- */
+
+  if (!p) {
+    p = pk->buf;
+    len = pk->len;
+  }
+  l = p + len - pk->buf;
+  o = 0;
+
+  /* --- Now grind through any packets which have accumulated --- */
+
+  pk->len = l;
+  while (l >= pk->want) {
+    size_t sz = pk->want;
+
+    /* --- Pass a packet to the user handler --- */
+
+    keep = 0;
+    pk->func(pk->buf + o, sz, pk, &keep, pk->p);
+
+    /* --- Adjust all the pointers for the next packet --- */
+
+    sz -= keep;
+    o += sz;
+    l -= sz;
+
+    /* --- Abort here if disabled --- */
+
+    if (!(pk->f & PKBUF_ENABLE))
+      break;
+  }
+
+  /* --- Shunt data around in the buffer --- */
+
+  if (o > 0 && l != 0)
+    memmove(pk->buf, pk->buf + o, l);
+  pk->len = l;
+}
+
+/* --- @pkbuf_close@ --- *
+ *
+ * Arguments:  @pkbuf *pk@ = pointer to buffer block
+ *
+ * Returns:    ---
+ *
+ * Use:                Informs the client that no more data is likely to arrive.  If
+ *             there is a partial packet in the buffer, it is discarded.
+ */
+
+void pkbuf_close(pkbuf *pk)
+{
+  if (pk->buf) {
+    x_free(pk->a, pk->buf);
+    pk->buf = 0;
+  }
+  pk->f |= PKBUF_CLOSE;
+  if (pk->f & PKBUF_ENABLE)
+    pk->func(0, 0, pk, 0, pk->p);
+}
+
+/* --- @pkbuf_free@ --- *
+ *
+ * Arguments:  @pkbuf *pk@ = pointer to buffer block
+ *             @octet **p@ = output pointer to free space
+ *
+ * Returns:    Free buffer size.
+ *
+ * Use:                Returns the free portion of a packet buffer.  Data can then
+ *             be written to this portion, and split out into packets by
+ *             calling @pkbuf_flush@.  A buffer is allocated if none
+ *             currently exists.
+ */
+
+size_t pkbuf_free(pkbuf *pk, octet **p)
+{
+  if (!pk->buf)
+    pk->buf = x_alloc(pk->a, pk->sz);
+  *p = pk->buf + pk->len;
+  return (pk->sz - pk->len);
+}
+
+/* --- @pkbuf_snarf@ --- *
+ *
+ * Arguments:  @pkbuf *pk@ = pointer to buffer block
+ *             @const void *p@ = pointer to input data buffer
+ *             @size_t sz@ = size of data in input buffer
+ *
+ * Returns:    ---
+ *
+ * Use:                Snarfs the data from the input buffer and spits it out as
+ *             packets.  This interface ignores the complexities of dealing
+ *             with disablement: you should be using @pkbuf_free@ to
+ *             contribute data if you want to cope with that.
+ */
+
+void pkbuf_snarf(pkbuf *pk, const void *p, size_t sz)
+{
+  const octet *pp = p;
+  while (sz && (pk->f & PKBUF_ENABLE)) {
+    size_t bsz;
+    octet *bp;
+
+    bsz = pkbuf_free(pk, &bp);
+    if (bsz > sz)
+      bsz = sz;
+    memcpy(bp, pp, bsz);
+    pkbuf_flush(pk, bp, bsz);
+    pp += bsz;
+    sz -= bsz;
+  }
+}
+
+/* --- @pkbuf_want@ --- *
+ *
+ * Arguments:  @pkbuf *pk@ = pointer to buffer block
+ *             @size_t want@ = how many octets wanted for next packet
+ *
+ * Returns:    ---
+ *
+ * Use:                Sets the desired size for the next packet to be read.  If
+ *             it's larger than the current buffer, the buffer is extended.
+ */
+
+void pkbuf_want(pkbuf *pk, size_t want)
+{
+  pk->want = want;
+  if (want > pk->sz) {
+    do pk->sz <<= 1; while (want > pk->sz);
+    if (pk->buf) {
+      if (pk->len)
+       pk->buf = x_realloc(pk->a, pk->buf, pk->sz, pk->len);
+      else {
+       x_free(pk->a, pk->buf);
+       pk->buf = 0;
+      }
+    }
+  }
+}
+
+/* --- @pkbuf_init@ --- *
+ *
+ * Arguments:  @pkbuf *pk@ = pointer to buffer block
+ *             @pkbuf *func@ = handler function
+ *             @void *p@ = argument pointer for @func@
+ *
+ * Returns:    ---
+ *
+ * Use:                Initializes a packet buffer block.  Any packets are passed to
+ *             the provided function for handling.
+ */
+
+void pkbuf_init(pkbuf *pk, pkbuf_func *func, void *p)
+{
+  pk->func = func;
+  pk->p = p;
+  pk->len = 0;
+  pk->f = PKBUF_ENABLE;
+  pk->buf = 0;
+  pk->sz = 256;
+  pk->want = 1;
+  pk->a = arena_global;
+}
+
+/* --- @pkbuf_destroy@ --- *
+ *
+ * Arguments:  @pkbuf *pk@ = pointer to buffer block
+ *
+ * Returns:    ---
+ *
+ * Use:                Deallocates a line buffer and frees any resources it owned.
+ */
+
+void pkbuf_destroy(pkbuf *pk)
+{
+  if (pk->buf) {
+    x_free(pk->a, pk->buf);
+    pk->buf = 0;
+  }
+}
+
+/*----- That's all, folks -------------------------------------------------*/
diff --git a/buf/pkbuf.h b/buf/pkbuf.h
new file mode 100644 (file)
index 0000000..1a8c325
--- /dev/null
@@ -0,0 +1,182 @@
+/* -*-c-*-
+ *
+ * Simple packet buffering
+ *
+ * (c) 2000 Straylight/Edgeware
+ */
+
+/*----- Licensing notice --------------------------------------------------*
+ *
+ * This file is part of the mLib utilities library.
+ *
+ * mLib is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * mLib is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with mLib; if not, write to the Free
+ * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+ * MA 02111-1307, USA.
+ */
+
+#ifndef MLIB_PKBUF_H
+#define MLIB_PKBUF_H
+
+#ifdef __cplusplus
+  extern "C" {
+#endif
+
+/*----- Header files ------------------------------------------------------*/
+
+#include <stddef.h>
+
+#ifndef MLIB_ARENA_H
+#  include "arena.h"
+#endif
+
+#ifndef MLIB_BITS_H
+#  include "bits.h"
+#endif
+
+/*----- Data structures ---------------------------------------------------*/
+
+struct pkbuf;
+
+typedef void pkbuf_func(octet */*b*/, size_t /*sz*/,
+                       struct pkbuf */*pk*/, size_t */*keep*/,
+                       void */*p*/);
+
+typedef struct pkbuf {
+  size_t sz;                           /* Size of current buffer */
+  size_t len;                          /* Length of data in the buffer */
+  size_t want;                         /* Want this many bytes for packet */
+  unsigned f;                          /* Various state flags */
+  pkbuf_func *func;                    /* Handler function */
+  void *p;                             /* Argument for handler */
+  arena *a;                            /* Memory allocation arena */
+  octet *buf;                          /* Actual buffer space */
+} pkbuf;
+
+#define PKBUF_ENABLE 1u                        /* Buffer is currently enabled */
+#define PKBUF_CLOSE 2u                 /* Buffer is now closed */
+
+/*----- Functions provided ------------------------------------------------*/
+
+/* --- @pkbuf_flush@ --- *
+ *
+ * Arguments:  @pkbuf *pk@ = pointer to buffer block
+ *             @octet *p@ = pointer to where to start searching
+ *             @size_t len@ = length of new material added
+ *
+ * Returns:    ---
+ *
+ * Use:                Flushes any complete packets in a packet buffer.  New
+ *             material is assumed to have been added starting at @p@.  If
+ *             @p@ is null, then the scan starts at the beginning of the
+ *             buffer, and the size of data already in the buffer is used in
+ *             place of @len@.
+ *
+ *             It is assumed that the buffer is initially enabled.  You
+ *             shouldn't be contributing data to a disabled buffer anyway.
+ *             However, the buffer handler may at some point disable itself,
+ *             and @pkbuf_flush@ can cope with this eventuality.  Any
+ *             pending data is left at the start of the buffer and can be
+ *             flushed out by calling @pkbuf_flush(b, 0, 0)@ if the buffer
+ *             is ever re-enabled.
+ */
+
+extern void pkbuf_flush(pkbuf */*pk*/, octet */*p*/, size_t /*len*/);
+
+/* --- @pkbuf_close@ --- *
+ *
+ * Arguments:  @pkbuf *pk@ = pointer to buffer block
+ *
+ * Returns:    ---
+ *
+ * Use:                Informs the client that no more data is likely to arrive.  If
+ *             there is a partial packet in the buffer, it is discarded.
+ */
+
+extern void pkbuf_close(pkbuf */*pk*/);
+
+/* --- @pkbuf_free@ --- *
+ *
+ * Arguments:  @pkbuf *pk@ = pointer to buffer block
+ *             @octet **p@ = output pointer to free space
+ *
+ * Returns:    Free buffer size.
+ *
+ * Use:                Returns the free portion of a packet buffer.  Data can then
+ *             be written to this portion, and split out into packets by
+ *             calling @pkbuf_flush@.
+ */
+
+extern size_t pkbuf_free(pkbuf */*pk*/, octet **/*p*/);
+
+/* --- @pkbuf_snarf@ --- *
+ *
+ * Arguments:  @pkbuf *pk@ = pointer to buffer block
+ *             @const void *p@ = pointer to input data buffer
+ *             @size_t sz@ = size of data in input buffer
+ *
+ * Returns:    ---
+ *
+ * Use:                Snarfs the data from the input buffer and spits it out as
+ *             packets.  This interface ignores the complexities of dealing
+ *             with disablement: you should be using @pkbuf_free@ to
+ *             contribute data if you want to cope with that.
+ */
+
+extern void pkbuf_snarf(pkbuf */*pk*/, const void */*p*/, size_t /*sz*/);
+
+/* --- @pkbuf_want@ --- *
+ *
+ * Arguments:  @pkbuf *pk@ = pointer to buffer block
+ *             @size_t want@ = how many octets wanted for next packet
+ *
+ * Returns:    ---
+ *
+ * Use:                Sets the desired size for the next packet to be read.  If
+ *             it's larger than the current buffer, the buffer is extended.
+ */
+
+extern void pkbuf_want(pkbuf */*pk*/, size_t /*want*/);
+
+/* --- @pkbuf_init@ --- *
+ *
+ * Arguments:  @pkbuf *pk@ = pointer to buffer block
+ *             @pkbuf_func *func@ = handler function
+ *             @void *p@ = argument pointer for @func@
+ *
+ * Returns:    ---
+ *
+ * Use:                Initializes a packet buffer block.  Any packets are passed to
+ *             the provided function for handling.
+ */
+
+extern void pkbuf_init(pkbuf */*pk*/, pkbuf_func */*func*/, void */*p*/);
+
+/* --- @pkbuf_destroy@ --- *
+ *
+ * Arguments:  @pkbuf *pk@ = pointer to buffer block
+ *
+ * Returns:    ---
+ *
+ * Use:                Deallocates a packet buffer and frees any resources it owned.
+ */
+
+extern void pkbuf_destroy(pkbuf */*pk*/);
+
+/*----- That's all, folks -------------------------------------------------*/
+
+#ifdef __cplusplus
+  }
+#endif
+
+#endif
diff --git a/codec/Makefile.am b/codec/Makefile.am
new file mode 100644 (file)
index 0000000..a37faec
--- /dev/null
@@ -0,0 +1,63 @@
+### -*-makefile-*-
+###
+### Build script for encoding and decoding
+###
+### (c) 2009 Straylight/Edgeware
+###
+
+###----- Licensing notice ---------------------------------------------------
+###
+### This file is part of the mLib utilities library.
+###
+### mLib is free software; you can redistribute it and/or modify
+### it under the terms of the GNU Library General Public License as
+### published by the Free Software Foundation; either version 2 of the
+### License, or (at your option) any later version.
+###
+### mLib is distributed in the hope that it will be useful,
+### but WITHOUT ANY WARRANTY; without even the implied warranty of
+### MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+### GNU Library General Public License for more details.
+###
+### You should have received a copy of the GNU Library General Public
+### License along with mLib; if not, write to the Free
+### Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+### MA 02111-1307, USA.
+
+include $(top_srcdir)/vars.am
+
+noinst_LTLIBRARIES      = libcodec.la
+libcodec_la_SOURCES     =
+
+###--------------------------------------------------------------------------
+### Component files.
+
+## Codec base.
+pkginclude_HEADERS     += codec.h
+libcodec_la_SOURCES    += codec.c
+LIBMANS                        += codec.3
+
+## null
+libcodec_la_SOURCES    += null-codec.c
+
+## Simple binary base-conversion codecs.
+pkginclude_HEADERS     += base64.h base32.h hex.h
+libcodec_la_SOURCES    += baseconv.c
+LIBMANS                        += base64.3
+
+## form-urlencoded
+pkginclude_HEADERS     += url.h
+libcodec_la_SOURCES    += url.c
+LIBMANS                        += url.3
+
+## Test program.
+bin_PROGRAMS           += bincode
+PROGMANS               += bincode.1
+
+bincode_SOURCES                 = bincode.c
+bincode_LDADD           = libcodec.la
+bincode_LDADD          += ../mem/libmem.la
+bincode_LDADD          += ../struct/libstruct.la
+bincode_LDADD          += $(UTIL_LIBS)
+
+###----- That's all, folks --------------------------------------------------
diff --git a/codec/base32.h b/codec/base32.h
new file mode 100644 (file)
index 0000000..3be8f8e
--- /dev/null
@@ -0,0 +1,112 @@
+/* -*-c-*-
+ *
+ * Base32 encoding and decoding
+ *
+ * (c) 1997 Straylight/Edgeware
+ */
+
+/*----- Licensing notice --------------------------------------------------*
+ *
+ * This file is part of the mLib utilities library.
+ *
+ * mLib is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * mLib is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with mLib; if not, write to the Free
+ * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+ * MA 02111-1307, USA.
+ */
+
+#ifndef MLIB_BASE32_H
+#define MLIB_BASE32_H
+
+#ifdef __cplusplus
+  extern "C" {
+#endif
+
+/*----- Header files ------------------------------------------------------*/
+
+#ifndef MLIB_CODEC_H
+#  include "codec.h"
+#endif
+
+#ifndef MLIB_DSTR_H
+#  include "dstr.h"
+#endif
+
+/*----- Data structures ---------------------------------------------------*/
+
+typedef struct base32_ctx {
+  unsigned long accl, acch;            /* Accumulator for output data */
+  unsigned qsz;                                /* Length of data queued */
+  unsigned lnlen;                      /* Length of the current line */
+  const char *indent;                  /* Newline-and-indent string */
+  unsigned maxline;                    /* Maximum permitted line length */
+} base32_ctx;
+
+/*----- Functions provided ------------------------------------------------*/
+
+/* --- @base32_encode@ --- *
+ *
+ * Arguments:  @base32_ctx *ctx@ = pointer to a context block
+ *             @const void *p@ = pointer to a source buffer
+ *             @size_t sz@ = size of the source buffer
+ *             @dstr *d@ = pointer to destination string
+ *
+ * Returns:    ---
+ *
+ * Use:                Encodes a binary string in base32.  To flush out the final
+ *             few characters (if necessary), pass a null source pointer.
+ */
+
+extern void base32_encode(base32_ctx */*ctx*/,
+                         const void */*p*/, size_t /*sz*/,
+                         dstr */*d*/);
+
+/* --- @base32_decode@ --- *
+ *
+ * Arguments:  @base32_ctx *ctx@ = pointer to a context block
+ *             @const void *p@ = pointer to a source buffer
+ *             @size_t sz@ = size of the source buffer
+ *             @dstr *d@ = pointer to destination string
+ *
+ * Returns:    ---
+ *
+ * Use:                Decodes a binary string in base32.  To flush out the final
+ *             few characters (if necessary), pass a null source pointer.
+ */
+
+extern void base32_decode(base32_ctx */*ctx*/,
+                         const void */*p*/, size_t /*sz*/,
+                         dstr */*d*/);
+
+/* --- @base32_init@ --- *
+ *
+ * Arguments:  @base32_ctx *ctx@ = pointer to context block to initialize
+ *
+ * Returns:    ---
+ *
+ * Use:                Initializes a base32 context properly.
+ */
+
+extern void base32_init(base32_ctx */*ctx*/);
+
+/*----- Codec object interface --------------------------------------------*/
+
+extern const codec_class base32_class, base32hex_class;
+
+/*----- That's all, folks -------------------------------------------------*/
+
+#ifdef __cplusplus
+  }
+#endif
+
+#endif
diff --git a/codec/base64.3 b/codec/base64.3
new file mode 100644 (file)
index 0000000..ddab190
--- /dev/null
@@ -0,0 +1,133 @@
+.\" -*-nroff-*-
+.TH base64 3 "20 June 1999" "Straylight/Edgeware" "mLib utilities library"
+.SH NAME
+base64, base32, hex \- obsolete binary encoding functions
+.\" @base64_encode
+.\" @base64_decode
+.\" @base64_init
+.\" @base32_encode
+.\" @base32_decode
+.\" @base32_init
+.\" @hex_encode
+.\" @hex_decode
+.\" @hex_init
+.SH SYNOPSIS
+.nf
+.B "#include <mLib/base64.h>"
+.B "#include <mLib/base32.h>"
+.B "#include <mLib/hex.h>"
+
+.BI "void base64_encode(base64_ctx *" ctx ,
+.BI "                   const void *" p ", size_t " sz ,
+.BI "                   dstr *" d );
+.BI "void base64_decode(base64_ctx *" ctx ,
+.BI "                   const void *" p ", size_t " sz ,
+.BI "                   dstr *" d );
+.BI "void base64_init(base64_ctx *" ctx );
+
+.BI "void base32_encode(base32_ctx *" ctx ,
+.BI "                   const void *" p ", size_t " sz ,
+.BI "                   dstr *" d );
+.BI "void base32_decode(base32_ctx *" ctx ,
+.BI "                   const void *" p ", size_t " sz ,
+.BI "                   dstr *" d );
+.BI "void base32_init(base32_ctx *" ctx );
+
+.BI "void hex_encode(hex_ctx *" ctx ,
+.BI "                const void *" p ", size_t " sz ,
+.BI "                dstr *" d );
+.BI "void hex_decode(hex_ctx *" ctx ,
+.BI "                const void *" p ", size_t " sz ,
+.BI "                dstr *" d );
+.BI "void hex_init(hex_ctx *" ctx );
+.fi
+.SH DESCRIPTION
+The
+.BR base64 ,
+.B base32
+and
+.B hex
+functions perform encoding and decoding of arbitrary binary strings, as
+defined by RFC4648, but without error reporting.  These functions are
+obsolete, and new applications should use the
+.BR codec (3)
+interface, which provides more encoding and decoding options, and proper
+error detection.
+.PP
+The interfaces to these sets of functions is very similar: in
+the following description,
+.I prefix
+stands for one of
+.BR base64 ,
+.BR base32 ,
+or
+.BR hex .
+.PP
+Before encoding or decoding a string, a
+.I context
+(of type
+.IB prefix _ctx \fR)
+must be initialized, by passing it to
+.IB prefix _init \fR.
+The context contains data which must be retained between calls to encode
+or decode substrings.  The
+.IB prefix _init
+function sets up initial values for the data, and sets up defaults for
+the output formatting settings (see below).
+.PP
+Encoding of a string is performed by the
+.IB prefix _encode
+function.  It is passed a pointer to a context block
+.IR ctx ,
+the input substring to encode passed by address
+.I p
+and length
+.IR sz ,
+and a pointer to a dynamic string
+.I d
+in which to write its output (see
+.BR dstr (3)
+for details on dynamic strings).  Once all the input data has been
+passed through
+.IB prefix _encode
+it is necessary to flush the final few bytes of output.  This is
+achieved by passing
+.I prefix _encode
+a null pointer as its source argument.  It is an error to attempt to
+continue encoding after flushing output.
+.PP
+The output of the
+.IB prefix _encode
+function is formatted into lines using values from the context
+structure.  The
+.B indent
+member is a pointer to a null-terminated string which is used to
+separate the output lines.  The default indent string contains only a
+newline character.  The
+.B maxline
+member gives the maximum length of line that
+.IB prefix _encode
+is allowed to produce.  If this is not a multiple of 4, it is rounded
+up to the next highest multiple of four before use.  A value of zero
+instructs
+.IB prefix _encode
+not to perform line splitting: the output will be a single (possibly
+very long) output line.  The default maximum line length is 72
+characters.  You may set these parameters by direct assignment to the
+context structure once it has been initialized.
+.PP
+Decoding is performed similarly by the
+.IB prefix _decode
+function.  The comments above about flushing output apply equally to
+decoding.
+.PP
+Decoding ignores all errors.  In particular, whitespace is ignored, and
+in the case of Base64 and Base32 encodings, it also ignores
+.RB ` = '
+characters in the string.
+.SH "SEE ALSO"
+.BR codec (3),
+.BR dstr (3),
+.BR mLib (3).
+.SH AUTHOR
+Mark Wooding, <mdw@distorted.org.uk>
diff --git a/codec/base64.h b/codec/base64.h
new file mode 100644 (file)
index 0000000..e868781
--- /dev/null
@@ -0,0 +1,112 @@
+/* -*-c-*-
+ *
+ * Base64 encoding and decoding
+ *
+ * (c) 1997 Straylight/Edgeware
+ */
+
+/*----- Licensing notice --------------------------------------------------*
+ *
+ * This file is part of the mLib utilities library.
+ *
+ * mLib is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * mLib is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with mLib; if not, write to the Free
+ * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+ * MA 02111-1307, USA.
+ */
+
+#ifndef MLIB_BASE64_H
+#define MLIB_BASE64_H
+
+#ifdef __cplusplus
+  extern "C" {
+#endif
+
+/*----- Header files ------------------------------------------------------*/
+
+#ifndef MLIB_CODEC_H
+#  include "codec.h"
+#endif
+
+#ifndef MLIB_DSTR_H
+#  include "dstr.h"
+#endif
+
+/*----- Data structures ---------------------------------------------------*/
+
+typedef struct base64_ctx {
+  unsigned long acc;                   /* Accumulator for output data */
+  unsigned qsz;                                /* Length of data queued */
+  unsigned lnlen;                      /* Length of the current line */
+  char *indent;                                /* Newline-and-indent string */
+  unsigned maxline;                    /* Maximum permitted line length */
+} base64_ctx;
+
+/*----- Functions provided ------------------------------------------------*/
+
+/* --- @base64_encode@ --- *
+ *
+ * Arguments:  @base64_ctx *ctx@ = pointer to a context block
+ *             @const void *p@ = pointer to a source buffer
+ *             @size_t sz@ = size of the source buffer
+ *             @dstr *d@ = pointer to destination string
+ *
+ * Returns:    ---
+ *
+ * Use:                Encodes a binary string in base64.  To flush out the final
+ *             few characters (if necessary), pass a null source pointer.
+ */
+
+extern void base64_encode(base64_ctx */*ctx*/,
+                         const void */*p*/, size_t /*sz*/,
+                         dstr */*d*/);
+
+/* --- @base64_decode@ --- *
+ *
+ * Arguments:  @base64_ctx *ctx@ = pointer to a context block
+ *             @const void *p@ = pointer to a source buffer
+ *             @size_t sz@ = size of the source buffer
+ *             @dstr *d@ = pointer to destination string
+ *
+ * Returns:    ---
+ *
+ * Use:                Decodes a binary string in base64.  To flush out the final
+ *             few characters (if necessary), pass a null source pointer.
+ */
+
+extern void base64_decode(base64_ctx */*ctx*/,
+                         const void */*p*/, size_t /*sz*/,
+                         dstr */*d*/);
+
+/* --- @base64_init@ --- *
+ *
+ * Arguments:  @base64_ctx *ctx@ = pointer to context block to initialize
+ *
+ * Returns:    ---
+ *
+ * Use:                Initializes a base64 context properly.
+ */
+
+extern void base64_init(base64_ctx */*ctx*/);
+
+/*----- Codec object interface --------------------------------------------*/
+
+extern const codec_class base64_class, file64_class, base64url_class;
+
+/*----- That's all, folks -------------------------------------------------*/
+
+#ifdef __cplusplus
+  }
+#endif
+
+#endif
diff --git a/codec/baseconv.c b/codec/baseconv.c
new file mode 100644 (file)
index 0000000..b7cb0a0
--- /dev/null
@@ -0,0 +1,495 @@
+/* -*-c-*-
+ *
+ * Binary base-conversion encoding and decoding (base64, base32, etc.)
+ *
+ * (c) 1997 Straylight/Edgeware
+ */
+
+/*----- Licensing notice --------------------------------------------------*
+ *
+ * This file is part of the mLib utilities library.
+ *
+ * mLib is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * mLib is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with mLib; if not, write to the Free
+ * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+ * MA 02111-1307, USA.
+ */
+
+/*----- Header files ------------------------------------------------------*/
+
+#include <ctype.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "alloc.h"
+#include "codec.h"
+#include "dstr.h"
+#include "macros.h"
+#include "sub.h"
+
+#include "base64.h"
+#include "base32.h"
+#include "hex.h"
+
+/*----- Important tables --------------------------------------------------*/
+
+/* --- Magic constants --- */
+
+#define NV -1                          /* Not valid */
+#define PC -2                          /* Padding character */
+#define NL -3                          /* Newline character */
+#define SP -4                          /* Space character */
+
+/* --- Base64 --- */
+
+static const char
+  encodemap_base64[] =    { "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+                           "abcdefghijklmnopqrstuvwxyz"
+                           "0123456789+/" },
+  encodemap_file64[] =    { "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+                           "abcdefghijklmnopqrstuvwxyz"
+                           "0123456789+%" },
+  encodemap_base64url[] = { "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+                           "abcdefghijklmnopqrstuvwxyz"
+                           "0123456789-_" };
+
+static const signed char decodemap_base64[] = {
+  NV, NV, NV, NV, NV, NV, NV, NV, NV, SP, NL, NV, SP, NL, NV, NV,  /* 0x */
+  NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV,  /* 1x */
+  SP, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, 62, NV, NV, NV, 63,  /* 2x */
+  52, 53, 54, 55, 56, 57, 58, 59, 60, 61, NV, NV, NV, PC, NV, NV,  /* 3x */
+  NV,  0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14,  /* 4x */
+  15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, NV, NV, NV, NV, NV,  /* 5x */
+  NV, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36 ,37, 38, 39, 40,  /* 6x */
+  41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, NV, NV, NV, NV, NV   /* 7x */
+}, decodemap_file64[] = {
+  NV, NV, NV, NV, NV, NV, NV, NV, NV, SP, NL, NV, SP, NL, NV, NV,  /* 0x */
+  NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV,  /* 1x */
+  SP, NV, NV, NV, NV, 63, NV, NV, NV, NV, NV, 62, NV, NV, NV, NV,  /* 2x */
+  52, 53, 54, 55, 56, 57, 58, 59, 60, 61, NV, NV, NV, PC, NV, NV,  /* 3x */
+  NV,  0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14,  /* 4x */
+  15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, NV, NV, NV, NV, NV,  /* 5x */
+  NV, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36 ,37, 38, 39, 40,  /* 6x */
+  41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, NV, NV, NV, NV, NV   /* 7x */
+}, decodemap_base64url[] = {
+  NV, NV, NV, NV, NV, NV, NV, NV, NV, SP, NL, NV, SP, NL, NV, NV,  /* 0x */
+  NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV,  /* 1x */
+  SP, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, 62, NV, NV,  /* 2x */
+  52, 53, 54, 55, 56, 57, 58, 59, 60, 61, NV, NV, NV, PC, NV, NV,  /* 3x */
+  NV,  0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14,  /* 4x */
+  15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, NV, NV, NV, NV, 63,  /* 5x */
+  NV, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36 ,37, 38, 39, 40,  /* 6x */
+  41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, NV, NV, NV, NV, NV   /* 7x */
+};
+
+/* --- Base32 --- */
+
+static const char
+  encodemap_base32[]   =    { "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567" },
+  encodemap_base32hex[] =   { "0123456789ABCDEFGHIJKLMNOPQRSTUV" };
+
+static const signed char decodemap_base32[] = {
+  NV, NV, NV, NV, NV, NV, NV, NV, NV, SP, NL, NV, SP, NL, NV, NV,  /* 0x */
+  NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV,  /* 1x */
+  SP, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV,  /* 2x */
+  NV, NV, 26, 27, 28, 29, 30, 31, NV, NV, NV, NV, NV, PC, NV, NV,  /* 3x */
+  NV,  0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14,  /* 4x */
+  15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, NV, NV, NV, NV, NV,  /* 5x */
+  NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV,  /* 6x */
+  NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV,  /* 7x */
+}, decodemap_base32hex[] = {
+  NV, NV, NV, NV, NV, NV, NV, NV, NV, SP, NL, NV, SP, NL, NV, NV,  /* 0x */
+  NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV,  /* 1x */
+  SP, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV,  /* 2x */
+   0,  1,  2,  3,  4,  5,  6,  7,  8,  9, NV, NV, NV, PC, NV, NV,  /* 3x */
+  NV, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24,  /* 4x */
+  25, 26, 27, 28, 29, 30, 31, NV, NV, NV, NV, NV, NV, NV, NV, NV,  /* 5x */
+  NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV,  /* 6x */
+  NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV,  /* 7x */
+};
+
+/* --- Hex --- */
+
+static const char
+  encodemap_hex[] =   { "0123456789ABCDEF" };
+
+static const signed char decodemap_hex[] = {
+  NV, NV, NV, NV, NV, NV, NV, NV, NV, SP, NL, NV, SP, NL, NV, NV,  /* 0x */
+  NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV,  /* 1x */
+  SP, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV,  /* 2x */
+   0,  1,  2,  3,  4,  5,  6,  7,  8,  9, NV, NV, NV, NV, NV, NV,  /* 3x */
+  NV, 10, 11, 12, 13, 14, 15, NV, NV, NV, NV, NV, NV, NV, NV, NV,  /* 4x */
+  NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV,  /* 5x */
+  NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV,  /* 6x */
+  NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV,  /* 7x */
+};
+
+/*----- Base conversion macros --------------------------------------------*/
+
+/* --- @BASECONV@ --- *
+ *
+ * Arguments:  @x@ = an input digit of width @IWD@ bits
+ *             @iwd@ = input digit width in bits
+ *             @owd@ = output digit width in bits
+ *             @put@ = function or macro to output a digit
+ *
+ * Use:                Inserts the bits of @x@ into an accumulator.  As digits @y@
+ *             of with @owd@ become ready, @put(y)@ is invoked to emit them.
+ */
+
+#define BASECONV(x, iwd, owd, put) do {                                        \
+  a = (a << iwd) | x;                                                  \
+  nb += iwd;                                                           \
+  while (nb >= owd) {                                                  \
+    nb -= owd;                                                         \
+    put((a >> nb) & ((1 << owd) - 1));                                 \
+  }                                                                    \
+} while (0)
+
+/* --- @BASECONV_FLUSH@ --- *
+ *
+ * Arguments:  @iwd@ = input digit width in bits
+ *             @owd@ = output digit width in bits
+ *             @put@ = function or macro to output a digit
+ *
+ * Use:                Flushes remaining digits from the base-conversion shift
+ *             register.  The bits in the shift register are padded on the
+ *             right with zeros.  Digits of width @owd@ are emitted by
+ *             invoking @put@.
+ */
+
+#define BASECONV_FLUSH(iwd, owd, put) do {                             \
+  if (nb) {                                                            \
+    while (nb < owd) { a <<= iwd; nb += iwd; }                         \
+    nb -= owd;                                                         \
+    put((a >> nb) & ((1 << owd) - 1));                                 \
+  }                                                                    \
+} while (0)
+
+/* --- @BASECONV_PAD@ --- *
+ *
+ * Arguments:  @iwd@ = input digit width in bits
+ *             @owd@ = output digit width in bits
+ *             @pad@ = function or macro to output padding
+ *
+ * Use:                Invokes @pad@ sufficiently often to realign the shift
+ *             register.
+ */
+
+#define BASECONV_PAD(iwd, owd, pad) do {                               \
+  for (;;) {                                                           \
+    while (nb >= owd) { pad; nb -= owd; }                              \
+    if (!nb) break;                                                    \
+    nb += iwd;                                                         \
+  }                                                                    \
+} while (0)
+
+#define NULL_PAD(iwd, owd, pad) do ; while (0)
+
+/*----- Lists of things to make -------------------------------------------*/
+
+#define CODECS(_)                                                      \
+  /* NAME,      CTXN,    ACC */                                                \
+  _(base64,    base64, acc)                                            \
+  _(file64,    base64, acc)                                            \
+  _(base64url, base64, acc)                                            \
+  _(base32,    base32, accl)                                           \
+  _(base32hex, base32, accl)                                           \
+  _(hex,       hex,    acc)
+
+#define CTXS(_)                                                                \
+  /* CTXN,     WD,     ACC */                                          \
+  _(base64,    6,      acc)                                            \
+  _(base32,    5,      accl)                                           \
+  _(hex,       4,      acc)
+
+#define base64_PADDING BASECONV_PAD
+#define base64_FLAGMASK ~(CDCF_LOWERC | CDCF_IGNCASE)
+#define base64_FLAGXOR 0
+#define base64_OLDFLAGS CDCF_IGNJUNK
+
+#define base32_PADDING BASECONV_PAD
+#define base32_FLAGMASK ~0
+#define base32_FLAGXOR 0
+#define base32_OLDFLAGS CDCF_IGNJUNK
+
+#define hex_PADDING NULL_PAD
+#define hex_FLAGMASK ~0
+#define hex_FLAGXOR 0
+#define hex_OLDFLAGS (CDCF_IGNJUNK | CDCF_LOWERC)
+
+/*----- Data structures ---------------------------------------------------*/
+
+#define OBJ(ctxn, wd, acc)                                             \
+                                                                       \
+typedef struct ctxn##_codec {                                          \
+  codec c;                                                             \
+  ctxn##_ctx ctx;                                                      \
+  const char *encodemap;                                               \
+  const signed char *decodemap;                                                \
+} ctxn##_codec;
+
+CTXS(OBJ)
+
+/*----- State packing -----------------------------------------------------*
+ *
+ * These macros convert between the state required by the new encoding and
+ * decoding core and the old externally-visible context structures.  It's
+ * unpleasant, I know; maybe we can drop the old interface later.
+ */
+
+enum {
+  ST_MAIN,                             /* Main decoding state */
+  ST_PAD,                              /* Decoding trailing padding */
+  ST_END                               /* Finished decoding */
+};
+
+#define STATE_UNPACK(acc)                                              \
+  unsigned long a = (ctx->acc >> 0) & 0xffff;                          \
+  unsigned nb = (ctx->acc >> 16) & 0xff;                               \
+  unsigned st = (ctx->acc >> 24) & 0xff;                               \
+  unsigned f = ctx->qsz;
+
+#define STATE_PACK(acc) do {                                           \
+  ctx->acc = (((a & 0xffff) << 0) |                                    \
+             (((unsigned long)nb & 0xff) << 16) |                      \
+             (((unsigned long)st & 0xff) << 24));                      \
+} while (0)
+
+/*----- Main encoder and decoder ------------------------------------------*/
+
+#define WRAP(stuff) do {                                               \
+  if (maxln && lnlen >= maxln) {                                       \
+    dstr_puts(d, ctx->indent);                                         \
+    lnlen = 0;                                                         \
+  }                                                                    \
+  stuff                                                                        \
+  lnlen++;                                                             \
+} while (0)
+
+#define PUTWRAP(x) WRAP({                                              \
+  char ch = encodemap[x];                                              \
+  if (f & CDCF_LOWERC) ch = TOLOWER(ch);                               \
+  DPUTC(d, ch);                                                                \
+})
+
+#define PADWRAP WRAP({ DPUTC(d, '='); })
+
+#define PUTRAW(x) DPUTC(d, x)
+
+#define ENCODER(ctxn, wd, acc)                                         \
+                                                                       \
+/* --- @CTXN_doencode@ --- *                                           \
+ *                                                                     \
+ * Arguments:  @CTXN_ctx *ctx@ = pointer to a context block            \
+ *             @const char *encodemap@ = pointer to encoding map       \
+ *             @const unsigned char *p@ = pointer to a source buffer   \
+ *             @size_t sz@ = size of the source buffer                 \
+ *             @dstr *d@ = pointer to destination string               \
+ *                                                                     \
+ * Returns:    Zero on success, or @CDCERR_@ error code.               \
+ *                                                                     \
+ * Use:                Main encoder function.                                  \
+ */                                                                    \
+                                                                       \
+static int ctxn##_doencode(ctxn##_ctx *ctx, const char *encodemap,     \
+                          const unsigned char *p, size_t sz, dstr *d)  \
+{                                                                      \
+  STATE_UNPACK(acc);                                                   \
+  const unsigned char *l = p + sz;                                     \
+  unsigned lnlen = ctx->lnlen, maxln = ctx->maxline;                   \
+                                                                       \
+  if (p) {                                                             \
+    while (p < l) BASECONV(*p++, 8, wd, PUTWRAP);                      \
+  } else {                                                             \
+    BASECONV_FLUSH(8, wd, PUTWRAP);                                    \
+    if (!(f & CDCF_NOEQPAD)) ctxn##_PADDING(8, wd, PADWRAP);           \
+  }                                                                    \
+                                                                       \
+  STATE_PACK(acc);                                                     \
+  ctx->lnlen = lnlen;                                                  \
+  return (0);                                                          \
+}                                                                      \
+                                                                       \
+/* --- @CTXN_dodecode@ --- *                                           \
+ *                                                                     \
+ * Arguments:  @CTXN_ctx *ctx@ = pointer to a context block            \
+ *             @const signed char *decodemap@ = pointer to decode map  \
+ *             @const char *p@ = pointer to a source buffer            \
+ *             @size_t sz@ = size of the source buffer                 \
+ *             @dstr *d@ = pointer to destination string               \
+ *                                                                     \
+ * Returns:    Zero on success, or @CDCERR_@ error code.               \
+ *                                                                     \
+ * Use:                Main decoder function.                                  \
+ */                                                                    \
+                                                                       \
+static int ctxn##_dodecode(ctxn##_ctx *ctx,                            \
+                          const signed char *decodemap,                \
+                          const unsigned char *p, size_t sz, dstr *d)  \
+{                                                                      \
+  STATE_UNPACK(acc);                                                   \
+  const unsigned char *l = p + sz;                                     \
+  int ch;                                                              \
+  int x;                                                               \
+                                                                       \
+  if (p) {                                                             \
+    while (p < l) {                                                    \
+      ch = *p++;                                                       \
+      switch (f & (CDCF_LOWERC | CDCF_IGNCASE)) {                      \
+       case 0:                                                         \
+         break;                                                        \
+       case CDCF_LOWERC:                                               \
+         if (ISUPPER(ch)) goto badch;                                  \
+       default:                                                        \
+         ch = TOUPPER(ch);                                             \
+      }                                                                        \
+      x = decodemap[ch];                                               \
+      switch (x) {                                                     \
+       case NV:                                                        \
+       badch:                                                          \
+         if (!(f & CDCF_IGNINVCH)) return (CDCERR_INVCH);              \
+         break;                                                        \
+       case PC:                                                        \
+         if (f & CDCF_IGNEQMID) break;                                 \
+         if (f & CDCF_NOEQPAD) goto badch;                             \
+         if (st == ST_MAIN &&                                          \
+             !(f & CDCF_IGNZPAD) && (a & ((1 << nb) - 1)))             \
+           return (CDCERR_INVZPAD);                                    \
+         st = ST_PAD;                                                  \
+         if (!(f & CDCF_IGNEQPAD)) {                                   \
+           if (!nb) return (CDCERR_INVEQPAD);                          \
+           nb = (nb + wd)%8;                                           \
+           st = ST_PAD;                                                \
+         }                                                             \
+         break;                                                        \
+       case NL:                                                        \
+         if (f & CDCF_IGNNEWL) break;                                  \
+         return (CDCERR_INVCH);                                        \
+       case SP:                                                        \
+         if (f & CDCF_IGNSPC) break;                                   \
+         return (CDCERR_INVCH);                                        \
+       default:                                                        \
+         if (st != ST_MAIN) return (CDCERR_INVEQPAD);                  \
+         BASECONV(x, wd, 8, PUTRAW);                                   \
+         break;                                                        \
+      }                                                                        \
+    }                                                                  \
+  } else {                                                             \
+    if (st == ST_MAIN &&                                               \
+       !(f & CDCF_IGNZPAD) && (a & ((1 << nb) - 1)))                   \
+      return (CDCERR_INVZPAD);                                         \
+    if (!(f & (CDCF_IGNEQPAD | CDCF_IGNEQMID | CDCF_NOEQPAD)) && nb)   \
+      return (CDCERR_INVEQPAD);                                                \
+  }                                                                    \
+                                                                       \
+  STATE_PACK(acc);                                                     \
+  return (0);                                                          \
+}
+
+CTXS(ENCODER)
+
+/*----- Codec implementation ----------------------------------------------*/
+
+#define OPS(ctxn, wd, acc)                                             \
+                                                                       \
+static int ctxn##_enc(codec *c, const void *p, size_t sz, dstr *d)     \
+{                                                                      \
+  ctxn##_codec *bc = (ctxn##_codec *)c;                                        \
+  return (ctxn##_doencode(&bc->ctx, bc->encodemap, p, sz, d));         \
+}                                                                      \
+                                                                       \
+static int ctxn##_dec(codec *c, const void *p, size_t sz, dstr *d)     \
+{                                                                      \
+  ctxn##_codec *bc = (ctxn##_codec *)c;                                        \
+  return (ctxn##_dodecode(&bc->ctx, bc->decodemap, p, sz, d));         \
+}                                                                      \
+                                                                       \
+static void ctxn##_destroy(codec *c)                                   \
+{                                                                      \
+  ctxn##_codec *bc = (ctxn##_codec *)c;                                        \
+  if (bc->ctx.indent) xfree((/*unconst*/ char *)bc->ctx.indent);       \
+  DESTROY(bc);                                                         \
+}                                                                      \
+                                                                       \
+static codec *ctxn##_docreate(unsigned flags,                          \
+                             const char *indent, unsigned maxline,     \
+                             const codec_ops *ops,                     \
+                             const char *encodemap,                    \
+                             const signed char *decodemap)             \
+{                                                                      \
+  ctxn##_codec *bc = CREATE(ctxn##_codec);                             \
+  bc->c.ops = ops;                                                     \
+  bc->ctx.acc = 0;                                                     \
+  bc->ctx.qsz = (flags & ctxn##_FLAGMASK) ^ ctxn##_FLAGXOR;            \
+  bc->ctx.lnlen = 0;                                                   \
+  bc->ctx.indent = indent ? xstrdup(indent) : 0;                       \
+  bc->ctx.maxline = maxline;                                           \
+  bc->encodemap = encodemap;                                           \
+  bc->decodemap = decodemap;                                           \
+  return (&bc->c);                                                     \
+}
+
+CTXS(OPS)
+
+#define CLASS(name, ctxn, acc)                                         \
+                                                                       \
+static const codec_ops                                                 \
+  name##_encode_ops = { &name##_class, ctxn##_enc, ctxn##_destroy },   \
+  name##_decode_ops = { &name##_class, ctxn##_dec, ctxn##_destroy };   \
+                                                                       \
+static codec *name##_encoder(unsigned flags,                           \
+                            const char *indent, unsigned maxline)      \
+{                                                                      \
+  return ctxn##_docreate(flags, indent, maxline,                       \
+                        &name##_encode_ops,                            \
+                        encodemap_##name,                              \
+                        decodemap_##name);                             \
+}                                                                      \
+                                                                       \
+static codec *name##_decoder(unsigned flags)                           \
+{                                                                      \
+  return ctxn##_docreate(flags, 0, 0,                                  \
+                        &name##_decode_ops,                            \
+                        encodemap_##name,                              \
+                        decodemap_##name);                             \
+}                                                                      \
+                                                                       \
+const codec_class name##_class =                                       \
+  { #name, name##_encoder, name##_decoder };
+
+CODECS(CLASS)
+
+/*----- Compatibility veneers ---------------------------------------------*/
+
+#define COMPAT(ctxn, wd, acc)                                          \
+                                                                       \
+void ctxn##_encode(ctxn##_ctx *ctx, const void *p, size_t sz, dstr *d) \
+  { ctxn##_doencode(ctx, encodemap_##ctxn, p, sz, d); }                        \
+                                                                       \
+void ctxn##_decode(ctxn##_ctx *ctx, const void *p, size_t sz, dstr *d) \
+  { ctxn##_dodecode(ctx, decodemap_##ctxn, p, sz, d); }                        \
+                                                                       \
+void ctxn##_init(ctxn##_ctx *ctx)                                      \
+{                                                                      \
+  ctx->acc = 0;                                                                \
+  ctx->qsz = (ctxn##_OLDFLAGS & ctxn##_FLAGMASK) ^ ctxn##_FLAGXOR;     \
+  ctx->lnlen = 0;                                                      \
+  ctx->indent = "\n";                                                  \
+  ctx->maxline = 72;                                                   \
+}
+
+CTXS(COMPAT)
+
+/*----- That's all, folks -------------------------------------------------*/
diff --git a/codec/bincode.1 b/codec/bincode.1
new file mode 100644 (file)
index 0000000..4e828b7
--- /dev/null
@@ -0,0 +1,109 @@
+.\" nroff
+.TH bincode 1 "9 January 2009" "Straylight/Edgeware" "mLib utilities library"
+.SH NAME
+bincode \- binary-to-text encoding and decoding
+.SH SYNOPSIS
+.B bincode
+.RB [ \-de]
+.RB [ \-f
+.IR flags ]
+.RB [ \-i
+.IR indent ]
+.RB [ \-m
+.IR maxline ]
+.RB [ \-o
+.IR output ]
+.RI [ file ...]
+.SH DESCRIPTION
+The
+.B bincode
+program encodes binary data as plain text (suitable, for example, for
+use in email), and recovers binary data from its encoding.
+.PP
+The options are as follows.
+.TP
+.B "\-h, \-\-help"
+Print a help message to standard output and exit successfully.
+.TP
+.B "\-v, \-\-version"
+Print the program's version number to standard output and exit
+successfully.
+.TP
+.B "\-u, \-\-usage"
+Print a one-line usage summary to standard output and exit successfully.
+.TP
+.BI "\-d, \-\-decode"
+Read encoded data and write the result of decoding it.
+.TP
+.BI "\-e, \-\-encode"
+Read raw binary data and write the result of encoding.
+.TP
+.BI "\-f, \-\-flags=" flags
+Set encoding/decoding flags.  The
+.I flags
+are a list of comma-separated flag names, each preceded by an optional
+.RB ` + '
+to set the flag (the default) or
+.RB ` \- '
+to clear it.  The flag names are as listed in
+.BR codec (3),
+but in lower case, and without the
+.RB ` CDCF_ '
+prefix; e.g.,
+.B CDCF_IGNNEWL
+may be specified as
+.RB ` ignnewl '.
+This option may be repeated: the options are scanned left-to-right.  The
+flags set by default are
+.RB ` ignspc '
+and
+.RB ` ignnewl '.
+.TP
+.BI "\-i, \-\-indent=" indent
+Insert the
+.I indent
+string before each line.  The string may contain simple
+backslash-escapes:
+.RB ` \ea ',
+.RB ` \eb ',
+.RB ` \ef ',
+.RB ` \en ',
+.RB ` \er ',
+.RB ` \et ', and
+.RB ` \ev '
+respectively stand for alert, backspace, form-feed, newline, carriage
+return, and horizontal and vertical tab.  A backslash preceding any
+other character yields that character; hence, to include a backslash,
+write a double backslash.  The default is the empty string: i.e., just
+end each line with a newline character.
+.TP
+.BI "\-m, \-\-maxline=" maxline
+Set the maximum output line length to
+.I maxline
+when encoding.  The limit is ignored when decoding.  If
+.I maxline
+is zero, then no line splitting is performed.
+.TP
+.BI "\-o, \-\-output=" output
+Write the (encoded or decoded) output to
+.IR output .
+The default is to write to standard output.  On platforms where it makes
+a difference, the output file is opened in text mode when encoding, and
+in binary mode when decoding.
+.PP
+The input to be encoded or decoded is the concatenation of the specified
+.IR file s.
+If no files are listed, then standard input is read.  A
+.I file
+which is a single
+.RB ` \- '
+also means to read standard input.  On systems where it makes a
+difference, named files are read in binary mode when encoding and in
+text mode when decoding.
+.PP
+If an error is encountered, the output may be partially written.
+.SH "SEE ALSO"
+.BR codec (3).
+.SH "AUTHOR"
+Mark Wooding, <mdw@distorted.org.uk>
+
diff --git a/codec/bincode.c b/codec/bincode.c
new file mode 100644 (file)
index 0000000..77d2457
--- /dev/null
@@ -0,0 +1,295 @@
+/* -*-c-*-
+ *
+ * Common test driver for encoding and decoding
+ *
+ * (c) 2009 Straylight/Edgeware
+ */
+
+/*----- Licensing notice --------------------------------------------------*
+ *
+ * This file is part of the mLib utilities library.
+ *
+ * mLib is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * mLib is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with mLib; if not, write to the Free
+ * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+ * MA 02111-1307, USA.
+ */
+
+/*----- Header files ------------------------------------------------------*/
+
+#include "config.h"
+
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "codec.h"
+#include "dstr.h"
+#include "macros.h"
+#include "mdwopt.h"
+#include "quis.h"
+#include "report.h"
+
+#include "base64.h"
+#include "base32.h"
+#include "hex.h"
+
+/*----- Static variables --------------------------------------------------*/
+
+static const codec_class *cctab[] = {
+  &base64_class,
+  &base64url_class,
+  &file64_class,
+  &base32_class,
+  &base32hex_class,
+  &hex_class,
+  &null_codec_class,
+  0,
+};
+
+static const struct { const char *name; unsigned f; } flagtab[] = {
+  { "lowerc",          CDCF_LOWERC },
+  { "igncase",         CDCF_IGNCASE },
+  { "noeqpad",         CDCF_NOEQPAD },
+  { "igneqpad",                CDCF_IGNEQPAD },
+  { "igneqmid",                CDCF_IGNEQMID },
+  { "ignzpad",         CDCF_IGNZPAD },
+  { "ignnewl",         CDCF_IGNNEWL },
+  { "ignspc",          CDCF_IGNSPC },
+  { "igninvch",                CDCF_IGNINVCH },
+  { "ignjunk",         CDCF_IGNJUNK },
+  { 0,                 0, }
+};
+
+/*----- Main code ---------------------------------------------------------*/
+
+static void usage(FILE *fp)
+{
+  pquis(fp,
+       "Usage: $ [-de] [-f FLAGS] [-i INDENT] [-m MAXLINE] [-o OUTPUT]\n"
+       "\tCODEC [FILE ...]\n");
+}
+
+static void version(FILE *fp)
+  { pquis(fp, "$, mLib version " VERSION "\n"); }
+
+static void help(FILE *fp)
+{
+  int i;
+
+  version(fp);
+  fputc('\n', fp);
+  usage(fp);
+  fputs("\n\
+Encodes and decodes binary files.  Options provided:\n\
+\n\
+-h, --help             Show this help text.\n\
+-v, --version          Show the program's version number.\n\
+-u, --usage            Show a terse usage message.\n\
+\n\
+-d, --decode           Decode binary FILEs.\n\
+-e, --encode           Encode binary FILEs (default).\n\
+-f, --flags=FLAGS      Set encoding/decoding FLAGS.\n\
+-i, --indent=INDENT    Indent each line with INDENT.\n\
+-m, --maxline=MAXLINE  Limit line length to (about) MAXLINE.\n\
+-o, --output=OUTPUT    Write encoded/decoded data to OUTPUT.\n\
+\n", fp);
+#define ENUM(label, tab, end, getname) do {                            \
+  fputs(label ":", fp);                                                        \
+  for (i = 0; tab[i]end; i++) fprintf(fp, " %s", tab[i]getname);       \
+  fputc('\n', fp);                                                     \
+} while (0)
+  ENUM("Codecs", cctab, != 0, ->name);
+  ENUM("Flags", flagtab, .name, .name);
+#undef ENUM
+}
+
+static void code(codec *c, const char *ifile, FILE *ifp, FILE *ofp)
+{
+  dstr d = DSTR_INIT;
+  char buf[4096];
+  size_t n;
+  int err;
+
+  do {
+    n = fread(buf, 1, sizeof(buf), ifp);
+    DRESET(&d);
+    if ((err = c->ops->code(c, buf, n, &d)) != 0)
+      die(EXIT_FAILURE, "decoding error: %s", codec_strerror(err));
+  } while (fwrite(d.buf, 1, d.len, ofp) == d.len && n == sizeof(buf));
+  if (ferror(ifp))
+    die(EXIT_FAILURE, "error reading `%s': %s", ifile, strerror(errno));
+}
+
+int main(int argc, char *argv[])
+{
+  enum { encode, decode };
+  int mode = encode;
+  const codec_class **cc;
+  codec *c;
+  const char *indent = "";
+  const char *imode, *omode, *ofile = 0;
+  unsigned maxline = 64;
+  unsigned f = CDCF_IGNSPC | CDCF_IGNNEWL;
+  const char *p;
+  char *q;
+  FILE *ifp, *ofp = stdout;
+  dstr d = DSTR_INIT;
+  int i;
+  int sense;
+  size_t n;
+  int err;
+
+#define f_bogus 32768u
+
+  ego(argv[0]);
+
+  for (;;) {
+    static const struct option opts[] = {
+      { "help",                0,              0,      'h' },
+      { "version",     0,              0,      'v' },
+      { "usage",       0,              0,      'u' },
+      { "encode",      0,              0,      'e' },
+      { "decode",      0,              0,      'd' },
+      { "indent",      OPTF_ARGREQ,    0,      'i' },
+      { "maxline",     OPTF_ARGREQ,    0,      'm' },
+      { "output",      OPTF_ARGREQ,    0,      'o' },
+      { "flags",       OPTF_ARGREQ,    0,      'f' },
+      { 0,             0,              0,      0 }
+    };
+
+    if ((i = mdwopt(argc, argv, "hvu" "edf:i:m:o:", opts, 0, 0, 0)) < 0)
+      break;
+    switch (i) {
+      case 'h': help(stdout); exit(0);
+      case 'v': version(stdout); exit(0);
+      case 'u': usage(stdout); exit(0);
+
+      case 'e': mode = encode; break;
+      case 'd': mode = decode; break;
+      case 'i': indent = optarg; break;
+      case 'o': ofile = optarg; break;
+
+      case 'm':
+       errno = 0;
+       maxline = strtoul(optarg, &q, 0);
+       if (*q || errno) die(EXIT_FAILURE, "bad integer `%s'", optarg);
+       break;
+
+      case 'f':
+       p = optarg;
+       while (*p) {
+         if (*p == '-') { sense = 0; p++; }
+         else if (*p == '+') { sense = 1; p++; }
+         else sense = 1;
+         n = strcspn(p, ",");
+         for (i = 0; flagtab[i].name; i++) {
+           if (strlen(flagtab[i].name) == n &&
+               STRNCMP(flagtab[i].name, ==, p, n))
+             goto found;
+         }
+         die(EXIT_FAILURE, "unknown flag `%.*s'", (int)n, p);
+       found:
+         if (sense) f |= flagtab[i].f;
+         else f &= ~flagtab[i].f;
+         p += n;
+         if (*p == ',') p++;
+       }
+       break;
+
+      default: f |= f_bogus; break;
+    }
+  }
+  argv += optind; argc -= optind;
+  if ((f & f_bogus) || !argc) { usage(stderr); exit(EXIT_FAILURE); }
+
+  for (cc = cctab;; cc++) {
+    if (!*cc) die(EXIT_FAILURE, "unknown codec `%s'", *argv);
+    else if (STRCMP(*argv, ==, (*cc)->name)) break;
+  }
+  argv++; argc--;
+
+  switch (mode) {
+    case encode:
+      DPUTC(&d, '\n');
+      for (p = indent;; p++) {
+       switch (*p) {
+         case '\\':
+           p++;
+           switch (*p) {
+             case 'a': DPUTC(&d, '\n'); break;
+             case 'b': DPUTC(&d, '\b'); break;
+             case 'f': DPUTC(&d, '\f'); break;
+             case 'n': DPUTC(&d, '\n'); break;
+             case 'r': DPUTC(&d, '\r'); break;
+             case 't': DPUTC(&d, '\t'); break;
+             case 'v': DPUTC(&d, '\v'); break;
+             case 0: goto done_indent; break;
+             default: DPUTC(&d, *p); break;
+           }
+           break;
+         case 0:
+           goto done_indent;
+         default:
+           DPUTC(&d, *p);
+           break;
+       }
+      }
+    done_indent:
+      DPUTZ(&d);
+      c = (*cc)->encoder(f, d.buf, maxline);
+      imode = "rb"; omode = "w";
+      break;
+    case decode:
+      c = (*cc)->decoder(f);
+      imode = "r"; omode = "wb";
+      break;
+    default:
+      abort();
+  }
+
+  if (ofile && (ofp = fopen(ofile, omode)) == 0) {
+    die(EXIT_FAILURE, "couldn't open `%s' for writing: %s",
+       ofile, strerror(errno));
+  }
+
+  if (mode == encode) fwrite(d.buf + 1, 1, d.len - 1, ofp);
+  if (!argc)
+    code(c, "<stdin>", stdin, ofp);
+  else for (i = 0; i < argc; i++) {
+    if (STRCMP(argv[i], ==, "-"))
+      code(c, "<stdin>", stdin, ofp);
+    else if ((ifp = fopen(argv[i], imode)) == 0) {
+      die(EXIT_FAILURE, "couldn't open `%s' for reading: %s",
+         argv[i], strerror(errno));
+    } else {
+      code(c, argv[i], ifp, ofp);
+      fclose(ifp);
+    }
+  }
+  DRESET(&d);
+  if ((err = c->ops->code(c, 0, 0, &d)) != 0)
+    die(EXIT_FAILURE, "decoding error: %s", codec_strerror(err));
+  if (mode == encode) DPUTC(&d, '\n');
+  fwrite(d.buf, 1, d.len, ofp);
+  c->ops->destroy(c); DDESTROY(&d);
+
+  if (ferror(ofp) || fflush(ofp) || fclose(ofp)) {
+    die(EXIT_FAILURE, "error writing to `%s': %s",
+       ofile ? ofile : "<stdout>", strerror(errno));
+  }
+  return (0);
+}
+
+/*----- That's all, folks -------------------------------------------------*/
diff --git a/codec/codec.3 b/codec/codec.3
new file mode 100644 (file)
index 0000000..fd797c5
--- /dev/null
@@ -0,0 +1,261 @@
+.\" -*-nroff-*-
+.TH codec 3 "9 January 2009" "Straylight/Edgeware" "mLib utilities library"
+.SH NAME
+codec \- binary encoding and decoding
+.\" @codec_class
+.\" @codec_strerror
+.\" @null_codec_class
+.\" @base64_class
+.\" @file64_class
+.\" @base64url_class
+.\" @base32_class
+.\" @base32hex_class
+.\" @hex_class
+.SH SYNOPSIS
+.nf
+.B "#include <mLib/codec.h>"
+.B "#include <mLib/base64.h>"
+.B "#include <mLib/base32.h>"
+.B "#include <mLib/hex.h>"
+
+.B "codec_class null_codec_class;"
+.B "codec_class base64_class, file64_class, base64url_class;"
+.B "codec_class base32_class, base32hex_class;"
+.B "codec_class hex_class;"
+
+.BI "const char *codec_strerror(int " err ");"
+.fi
+.SH DESCRIPTION
+The
+.B codec
+system provides an object-based interface to functions which encode
+binary data as plain text and decode the result to recover the original
+binary data.  The interface makes it easy to support multiple encodings
+and select an appropriate one at runtime.
+.SS "The codec_class structure"
+The
+.B codec_class
+structure represents a particular encoding format.  The structure has
+the following members.
+.TP
+.B "const char *name"
+The name of the class, as a null-terminated string.  The name should not
+contain whitespace characters.
+.TP
+.BI "codec *(*encoder)(unsigned " flags ", const char *" indent ", unsigned " maxline ")"
+Pointer to a function which constructs a new encoder object, of type
+.BR codec .
+The
+.I flags
+configure the behaviour of the object; the
+.I indent
+string is written to separate lines of output; the integer
+.I maxline
+is the maximum length of line to be produced, or zero to forbid line
+breaking.
+.TP
+.BI "codec *(*decoder)(unsigned " flags ")"
+Pointer to a function which constructs a new decoder object, also of
+type
+.BR codec .
+The
+.I flags
+configure the behaviour of the object.
+.PP
+The
+.I flags
+to the
+.B encoder
+and
+.B decoder
+functions have the following meanings.
+.TP
+.B CDCF_LOWERC
+For codecs which produce output using a single alphabetic case (e.g.,
+.BR base32 ,
+.BR hex ),
+emit and accept only lower case; the default to emit and accept only
+upper case, for compatibility with RFC4648.  If the codec usually
+produces mixed-case output, then this flag is ignored.
+.TP
+.B CDCF_IGNCASE
+For codecs which produce output using a single alphabetic case, ignore
+the case of the input when decoding.  If the codec usually produces
+mixed-case output, then this flag is ignored.
+.TP
+.B CDCF_NOEQPAD
+For codecs which usually pad their output (e.g.,
+.BR base64 ,
+.BR base32 ),
+do not emit or accept padding characters.  If the codec does not usually
+produce padding, or the padding is not redundant, then this flag is
+ignored.
+.TP
+.B CDCF_IGNEQPAD
+For codecs which usually pad their output, do not treat incorrect (e.g.,
+missing or excessive) padding as an error when decoding.  If the codec
+does not usually produce padding, or the padding is required for
+unambiguous decoding, then this flag is ignored.
+.TP
+.B CDCF_IGNEQMID
+For codecs which usually pad their output, ignore padding characters
+wherever they may appear when decoding.  Usually padding characters
+indicate the end of the input, and further input characters are
+considered erroneous.  If the codec does not usually produce padding, or
+it is impossible to resume decoding correctly having seen padding
+characters, then this flag is ignored.
+.TP
+.B CDCF_IGNZPAD
+For codecs which need to pad their input, ignore unusual padding bits
+when decoding.  (This is not at all the same thing as the padding
+characters controlled by the flags above: they deal with padding the
+length of the encoding
+.I output
+up to a suitable multiple of characters; this option deals with padding
+of the
+.I input
+prior to encoding.)  If the codec does not add padding bits, or specific
+values are required for unambiguous decoding, then this flag is ignored.
+.TP
+.B CDCF_IGNNEWL
+Ignore newline (and carriage-return) characters when decoding: the
+default for RFC4648 codecs is to reject newline characters.  If these
+characters are significant in the encoding, then this flag is ignored.
+.TP
+.B CDCF_IGNSPC
+Ignore whitespace characters (other than newlines) when decoding: the
+default for RFC4648 codecs is to reject whitespace characters.  If these
+characters are significant in the encoding, then this flag is ignored.
+.TP
+.B CDCF_IGNINVCH
+Ignore any other invalid characters appearing in the input when
+decoding.
+.TP
+.B CDCF_IGNJUNK
+Ignore all `junk' in the input.  This should suppress almost all
+decoding errors.
+.PP
+If you do not set any of the
+.BR CDCF_IGN ...\&
+flags, a decoder should only accept the exact encoding that the
+corresponding encoder would produce (with
+.I maxline
+= 0 to inhibit line-breaking).
+.SS "The codec and codec_ops structures"
+The
+.B codec
+structure represents the state of an encoder or decoder, as returned by
+the
+.B encoder
+and
+.B decoder
+functions described above, contains a single member.
+.TP
+.B "const codec_ops *ops"
+Pointer to a
+.B codec_ops
+structure which contains operations and metadata for use with the
+encoder or decoder.
+.PP
+The
+.B codec_ops
+structure contains the following members.
+.TP
+.B "const codec_class *c"
+Pointer back to the
+.B codec_class
+which was used to construct the
+.B codec
+object.
+.TP
+.BI "int (*code)(codec *" c ", const void *" p ", size_t " sz ", dstr *" d ")"
+Encode or decode, using the codec
+.IR c ,
+the data in the buffer at address
+.I p
+and continuing for
+.I sz
+bytes, appending the output to the dynamic string
+.I d
+(see
+.BR dstr (3)).
+If the operation was successful, the function returns zero; otherwise it
+returns a nonzero error code, as described below.
+.TP
+.BI "void (*destroy)(codec *" c ")"
+Destroy the codec object
+.IR c ,
+freeing any resources it may hold.
+.PP
+A codec may buffer its input (e.g., if needs to see more in order to
+decide what output to produce next); it may also need to take special
+action at the end of the input (e.g., flushing buffers, and applying
+padding).  To signal the codec that there is no more input, call the
+.B code
+function with a null
+.I p
+pointer.  It will then write any final output to
+.IR d .
+.PP
+The following error conditions may be reported.
+.TP
+.B CDCERR_INVCH
+An invalid character was encountered while decoding.  This includes
+encoutering padding characters if padding is disabled using the
+.B CDCF_NOEQPAD
+flag.
+.TP
+.B CDCERR_INVEQPAD
+Invalid padding characters (e.g., wrong characters, or too few, too
+many, or none at all) were found during decoding.  This may also
+indicate that the input is truncated, even if the codec does not usually
+perform output padding.
+.TP
+.B CDCERR_INVZPAD
+Invalid padding bits were found during decoding.
+.PP
+The
+.B codec_strerror
+function converts these error codes to brief, (moderately)
+human-readable strings.
+.SS "Provided codecs"
+The library provides a number of standard codecs.
+.TP
+.B base64
+Implements Base64 encoding, as defined by RFC4648.  Output is
+mixed-case, so the
+.B CDCF_LOWERC
+and
+.B CDCF_IGNCASE
+flags are ignored.
+.TP
+.B safe64
+Implements a variant of the Base64 encoding which uses
+.RB ` % '
+in place of
+.RB ` / ',
+so that its output is suitable for use as a Unix filename.
+.TP
+.B base64url
+Implements the filename- and URL-safe variant of Base64 encoding, as
+defined by RFC4648.
+.TP
+.B base32
+Implements Base32 encoding, as defined by RFC4648.  Output is in upper
+case by default.
+.TP
+.B base32hex
+Implements the extended-hex variant of Base32, as defined by RFC4648.
+This encoding has the property that the encoding preserves the ordering
+of messages if padding is suppressed.
+.TP
+.B hex
+Implements hex encoding, defined by RFC4648 under the name Base16.  For
+compatibility with that specification, output is in upper case by
+default.
+.SH "SEE ALSO"
+.BR bincode (1),
+.BR dstr (3),
+.BR mLib (3).
+.SH AUTHOR
+Mark Wooding, <mdw@distorted.org.uk>
diff --git a/codec/codec.c b/codec/codec.c
new file mode 100644 (file)
index 0000000..8c284be
--- /dev/null
@@ -0,0 +1,58 @@
+/* -*-c-*-
+ *
+ * Binary codec utilities
+ *
+ * (c) 2009 Straylight/Edgeware
+ */
+
+/*----- Licensing notice --------------------------------------------------*
+ *
+ * This file is part of the mLib utilities library.
+ *
+ * mLib is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * mLib is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with mLib; if not, write to the Free
+ * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+ * MA 02111-1307, USA.
+ */
+
+/*----- Header files ------------------------------------------------------*/
+
+#include "codec.h"
+#include "macros.h"
+
+/*----- Static variables --------------------------------------------------*/
+
+static const char *errtab[] = {
+#define ERR(name, text) text,
+  CODEC_ERRORS(ERR)
+};
+
+/*----- Main code ---------------------------------------------------------*/
+
+/* --- @codec_strerror@ --- *
+ *
+ * Arguments:  @int err@ = error code from codec
+ *
+ * Returns:    Pointer to error text.
+ *
+ * Use:                Converts error codes to human-readable strings.
+ */
+
+const char *codec_strerror(int err)
+{
+  if (err < 0 || err >= N(errtab))
+    return ("Error code out of range");
+  return (errtab[err]);
+}
+
+/*----- That's all, folks -------------------------------------------------*/
diff --git a/codec/codec.h b/codec/codec.h
new file mode 100644 (file)
index 0000000..6155d5a
--- /dev/null
@@ -0,0 +1,110 @@
+/* -*-c-*-
+ *
+ * Abstract interface to codecs
+ *
+ * (c) 2009 Straylight/Edgeware
+ */
+
+/*----- Licensing notice --------------------------------------------------*
+ *
+ * This file is part of the mLib utilities library.
+ *
+ * mLib is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * mLib is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with mLib; if not, write to the Free
+ * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+ * MA 02111-1307, USA.
+ */
+
+#ifndef MLIB_CODEC_H
+#define MLIB_CODEC_H
+
+#ifdef __cplusplus
+  extern "C" {
+#endif
+
+/*----- Header files ------------------------------------------------------*/
+
+#ifndef MLIB_DSTR_H
+#  include "dstr.h"
+#endif
+
+/*----- Data structures ---------------------------------------------------*/
+
+typedef struct codec {
+  const struct codec_ops *ops;
+} codec;
+
+typedef struct codec_class {
+  const char *name;
+  codec *(*encoder)(unsigned /*flags*/,
+                   const char */*indent*/, unsigned /*maxline*/);
+  codec *(*decoder)(unsigned /*flags*/);
+
+#define CDCF_LOWERC 1u                 /* Prefer lower case */
+#define CDCF_IGNCASE 2u                        /* Ignore case on input */
+#define CDCF_NOEQPAD 4u                        /* No `=' padding */
+#define CDCF_IGNEQPAD 8u               /* Ignore `=' pad errors on input */
+#define CDCF_IGNEQMID 16u              /* Ignore pad chars on input */
+#define CDCF_IGNZPAD 32u               /* Ignore zero padding on input */
+#define CDCF_IGNNEWL 64u               /* Ignore newlines on input */
+#define CDCF_IGNINVCH 128u             /* Ignore invalid chars on input */
+#define CDCF_IGNSPC 256u               /* Ignore whitespace on input */
+
+#define CDCF_IGNJUNK                   /* Ignore all bad things */     \
+  (CDCF_IGNEQMID | CDCF_IGNZPAD |                                      \
+   CDCF_IGNCASE | CDCF_IGNNEWL | CDCF_IGNSPC | CDCF_IGNINVCH)
+
+} codec_class;
+
+#define CODEC_ERRORS(_)                                                        \
+  _(OK,                "No error")                                             \
+  _(INVCH,     "Invalid character")                                    \
+  _(INVEQPAD,  "Invalid padding character")                            \
+  _(INVZPAD,   "Nonzero padding bits")
+enum {
+#define DECLERR(name, text) CDCERR_##name,
+  CODEC_ERRORS(DECLERR)
+#undef DECLERR
+  CDCERR__LIMIT
+};
+
+typedef struct codec_ops {
+  const codec_class *c;
+  int (*code)(codec */*c*/, const void */*p*/, size_t /*sz*/, dstr */*d*/);
+  void (*destroy)(codec */*c*/);
+} codec_ops;
+
+/*----- Functions provided ------------------------------------------------*/
+
+/* --- @codec_strerror@ --- *
+ *
+ * Arguments:  @int err@ = error code from codec
+ *
+ * Returns:    Pointer to error text.
+ *
+ * Use:                Converts error codes to human-readable strings.
+ */
+
+const char *codec_strerror(int /*err*/);
+
+/*----- Null codec --------------------------------------------------------*/
+
+extern const codec_class null_codec_class;
+
+/*----- That's all, folks -------------------------------------------------*/
+
+#ifdef __cplusplus
+  }
+#endif
+
+#endif
diff --git a/codec/hex.h b/codec/hex.h
new file mode 100644 (file)
index 0000000..db79530
--- /dev/null
@@ -0,0 +1,109 @@
+/* -*-c-*-
+ *
+ * Hexadecimal encoding and decoding
+ *
+ * (c) 2001 Straylight/Edgeware
+ */
+
+/*----- Licensing notice --------------------------------------------------*
+ *
+ * This file is part of the mLib utilities library.
+ *
+ * mLib is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * mLib is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with mLib; if not, write to the Free
+ * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+ * MA 02111-1307, USA.
+ */
+
+#ifndef MLIB_HEX_H
+#define MLIB_HEX_H
+
+#ifdef __cplusplus
+  extern "C" {
+#endif
+
+/*----- Header files ------------------------------------------------------*/
+
+#ifndef MLIB_CODEC_H
+#  include "codec.h"
+#endif
+
+#ifndef MLIB_DSTR_H
+#  include "dstr.h"
+#endif
+
+/*----- Data structures ---------------------------------------------------*/
+
+typedef struct hex_ctx {
+  unsigned long acc;                   /* Accumulator for output data */
+  unsigned qsz;                                /* Length of data queued */
+  unsigned lnlen;                      /* Length of the current line */
+  const char *indent;                  /* Newline-and-indent string */
+  unsigned maxline;                    /* Maximum permitted line length */
+} hex_ctx;
+
+/*----- Functions provided ------------------------------------------------*/
+
+/* --- @hex_encode@ --- *
+ *
+ * Arguments:  @hex_ctx *ctx@ = pointer to a context block
+ *             @const void *p@ = pointer to a source buffer
+ *             @size_t sz@ = size of the source buffer
+ *             @dstr *d@ = pointer to destination string
+ *
+ * Returns:    ---
+ *
+ * Use:                Encodes a binary string in hex.
+ */
+
+extern void hex_encode(hex_ctx */*ctx*/, const void */*p*/, size_t /*sz*/,
+                      dstr */*d*/);
+
+/* --- @hex_decode@ --- *
+ *
+ * Arguments:  @hex_ctx *ctx@ = pointer to a context block
+ *             @const void *p@ = pointer to a source buffer
+ *             @size_t sz@ = size of the source buffer
+ *             @dstr *d@ = pointer to destination string
+ *
+ * Returns:    ---
+ *
+ * Use:                Decodes a binary string in hex.  Pass in a null source
+ *             pointer when you thing you've finished.
+ */
+
+extern void hex_decode(hex_ctx */*ctx*/, const void */*p*/, size_t /*sz*/,
+                      dstr */*d*/);
+
+/* --- @hex_init@ --- *
+ *
+ * Arguments:  @hex_ctx *ctx@ = pointer to context block to initialize
+ *
+ * Returns:    ---
+ *
+ * Use:                Initializes a hex context properly.
+ */
+
+extern void hex_init(hex_ctx */*ctx*/);
+
+/*----- Codec object interface --------------------------------------------*/
+
+extern const codec_class hex_class;
+
+/*----- That's all, folks -------------------------------------------------*/
+
+#ifdef __cplusplus
+  }
+#endif
+
+#endif
diff --git a/codec/null-codec.c b/codec/null-codec.c
new file mode 100644 (file)
index 0000000..1f5493e
--- /dev/null
@@ -0,0 +1,67 @@
+/* -*-c-*-
+ *
+ * Null codec implementation
+ *
+ * (c) 2009 Straylight/Edgeware
+ */
+
+/*----- Licensing notice --------------------------------------------------*
+ *
+ * This file is part of the mLib utilities library.
+ *
+ * mLib is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * mLib is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with mLib; if not, write to the Free
+ * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+ * MA 02111-1307, USA.
+ */
+
+/*----- Header files ------------------------------------------------------*/
+
+#include <string.h>
+
+#include "codec.h"
+#include "sub.h"
+
+/*----- Data structures ---------------------------------------------------*/
+
+typedef struct null_codec {
+  codec c;
+} null_codec;
+
+/*----- Main code ---------------------------------------------------------*/
+
+static int encdec(codec *c, const void *p, size_t sz, dstr *d)
+  { DPUTM(d, p, sz); return (0); }
+
+static void destroy(codec *c)
+  { null_codec *nc = (null_codec *)c; DESTROY(nc); }
+
+static const codec_ops ops = { &null_codec_class, encdec, destroy };
+
+static codec *encoder(unsigned flags, const char *indent, unsigned maxlen)
+{
+  null_codec *nc = CREATE(null_codec);
+  nc->c.ops = &ops;
+  return (&nc->c);
+}
+
+static codec *decoder(unsigned flags)
+{
+  null_codec *nc = CREATE(null_codec);
+  nc->c.ops = &ops;
+  return (&nc->c);
+}
+
+const codec_class null_codec_class = { "null", encoder, decoder };
+
+/*----- That's all, folks -------------------------------------------------*/
diff --git a/codec/tests.at b/codec/tests.at
new file mode 100644 (file)
index 0000000..bc93d3a
--- /dev/null
@@ -0,0 +1,180 @@
+### -*-autotest-*-
+###
+### Test script for utilities
+###
+### (c) 2009 Straylight/Edgeware
+###
+
+###----- Licensing notice ---------------------------------------------------
+###
+### This file is part of the mLib utilities library.
+###
+### mLib is free software; you can redistribute it and/or modify
+### it under the terms of the GNU Library General Public License as
+### published by the Free Software Foundation; either version 2 of the
+### License, or (at your option) any later version.
+###
+### mLib is distributed in the hope that it will be useful,
+### but WITHOUT ANY WARRANTY; without even the implied warranty of
+### MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+### GNU Library General Public License for more details.
+###
+### You should have received a copy of the GNU Library General Public
+### License along with mLib; if not, write to the Free
+### Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+### MA 02111-1307, USA.
+
+###--------------------------------------------------------------------------
+### Useful macros.
+
+m4_define([NEWLINE], [
+])
+
+## CODEC_TESTENCODE(codec, raw-data, encoding, [options])
+##
+## Check that CODEC encodes RAW-DATA as ENCODING, and that it can decode
+## ENCODING as RAW-DATA, using OPTIONS.
+m4_define([CODEC_TESTENCODE], [
+  printf '%s' '$2' >in
+  printf '%s\n' '$3' >expout
+  AT_CHECK([BUILDDIR/bincode $4 $1 in], [0], [expout])
+  mv in expout
+  printf '%s' '$3' >in
+  AT_CHECK([BUILDDIR/bincode -d $4 $1 in], [0], [expout])
+])
+
+## CODEC_TESTDECODE(codec, encoding, [raw-data],
+##                 [options], [exp-status], [exp-stderr])
+##
+## Check that CODEC decodes ENCODING as RAW-DATA, given the OPTIONS, or that
+## it reports EXP-STATUS and produces EXP-STDERR.
+m4_define([CODEC_TESTDECODE], [
+  printf '%s' '$2' >in
+  printf '%s' '$3' >expout
+  AT_CHECK([BUILDDIR/bincode -d $4 $1 in], [$5], [expout],
+          m4_if([$6], [], [], [bincode: decoding error: $6[]NEWLINE]))
+])
+
+###--------------------------------------------------------------------------
+### base64
+AT_SETUP([codec: base64])
+AT_KEYWORDS([codec base64])
+
+## Test vectors from RFC4648.
+CODEC_TESTENCODE([base64],     [],             [])
+CODEC_TESTENCODE([base64],     [f],            [Zg==])
+CODEC_TESTENCODE([base64],     [fo],           [Zm8=])
+CODEC_TESTENCODE([base64],     [foo],          [Zm9v])
+CODEC_TESTENCODE([base64],     [foob],         [Zm9vYg==])
+CODEC_TESTENCODE([base64],     [fooba],        [Zm9vYmE=])
+CODEC_TESTENCODE([base64],     [foobar],       [Zm9vYmFy])
+
+## Test vectors for Base64-URL.
+CODEC_TESTENCODE([base64url],  [fooba],        [Zm9vYmE=])
+CODEC_TESTENCODE([base64],     [fooba?],       [Zm9vYmE/])
+CODEC_TESTENCODE([base64url],  [fooba?],       [Zm9vYmE_])
+CODEC_TESTENCODE([file64],     [fooba?],       [Zm9vYmE%])
+CODEC_TESTENCODE([base64],     [fooba~],       [Zm9vYmF+])
+CODEC_TESTENCODE([base64url],  [fooba~],       [Zm9vYmF-])
+CODEC_TESTENCODE([file64],     [fooba~],       [Zm9vYmF+])
+
+## Some noeqpad tests.
+CODEC_TESTENCODE([base64],     [f],            [Zg],           [-fnoeqpad])
+CODEC_TESTENCODE([base64],     [foo],          [Zm9v],         [-fnoeqpad])
+CODEC_TESTENCODE([base64],     [foob],         [Zm9vYg],       [-fnoeqpad])
+
+## Test for pad-character errors.
+CODEC_TESTDECODE([base64],     [Zg],           [f],            [],
+                [1], [Invalid padding character])
+CODEC_TESTDECODE([base64],     [Zg],           [f],            [-figneqpad])
+CODEC_TESTDECODE([base64],     [Zg=],          [f],            [-figneqpad])
+CODEC_TESTDECODE([base64],     [Zg==],         [f],            [-figneqpad])
+CODEC_TESTDECODE([base64],     [Zg],           [f],            [-fnoeqpad])
+CODEC_TESTDECODE([base64],     [Zg=],          [],             [-fnoeqpad],
+                [1], [Invalid character])
+CODEC_TESTDECODE([base64],     [Zg],           [f],            [-figneqmid])
+CODEC_TESTDECODE([base64],     [Z==g=],        [f],            [-figneqmid])
+
+## Test for other crappy characters.
+CODEC_TESTDECODE([base64],     [Z:g=:=],       [],             [],
+                [1], [Invalid character])
+CODEC_TESTDECODE([base64],     [Z:g=:=],       [f],            [-figninvch])
+CODEC_TESTDECODE([base64],     [Z:g=:==],      [],             [-figninvch],
+                [1], [Invalid padding character])
+
+## Test for incorrect padding bits.
+CODEC_TESTDECODE([base64],     [Zh==],         [],             [],
+                [1], [Nonzero padding bits])
+CODEC_TESTDECODE([base64],     [Zh==],         [f],            [-fignzpad])
+
+## Make sure the case flags are suppressed.
+CODEC_TESTENCODE([base64],     [fooba],        [Zm9vYmE=],     [-flowerc])
+
+## Multiline formatting.
+AT_DATA([bigfile],
+[There are three infallible ways of pleasing an author, and the three form a
+rising scale of compliment: 1, to tell him you have read one of his books; 2,
+to tell him you have read all of his books; 3, to ask him to let you read the
+manuscript of his forthcoming book.  No. 1 admits you to his respect; No. 2
+admits you to his admiration; No. 3 carries you clear into his heart.
+                -- Mark Twain, "Pudd'nhead Wilson's Calendar"
+])
+
+AT_DATA([bigfile.b64],
+[VGhlcmUgYXJlIHRocmVlIGluZmFsbGlibGUgd2F5cyBvZiBwbGVhc2luZyBhbiBh
+dXRob3IsIGFuZCB0aGUgdGhyZWUgZm9ybSBhCnJpc2luZyBzY2FsZSBvZiBjb21w
+bGltZW50OiAxLCB0byB0ZWxsIGhpbSB5b3UgaGF2ZSByZWFkIG9uZSBvZiBoaXMg
+Ym9va3M7IDIsCnRvIHRlbGwgaGltIHlvdSBoYXZlIHJlYWQgYWxsIG9mIGhpcyBi
+b29rczsgMywgdG8gYXNrIGhpbSB0byBsZXQgeW91IHJlYWQgdGhlCm1hbnVzY3Jp
+cHQgb2YgaGlzIGZvcnRoY29taW5nIGJvb2suICBOby4gMSBhZG1pdHMgeW91IHRv
+IGhpcyByZXNwZWN0OyBOby4gMgphZG1pdHMgeW91IHRvIGhpcyBhZG1pcmF0aW9u
+OyBOby4gMyBjYXJyaWVzIHlvdSBjbGVhciBpbnRvIGhpcyBoZWFydC4KICAgICAg
+ICAgICAgICAgIC0tIE1hcmsgVHdhaW4sICJQdWRkJ25oZWFkIFdpbHNvbidzIENh
+bGVuZGFyIgo=
+])
+cp bigfile.b64 expout
+AT_CHECK([BUILDDIR/bincode base64 bigfile], [0], [expout])
+AT_CHECK([BUILDDIR/bincode -f-ignnewl -d base64 bigfile.b64], [1], [ignore],
+        [bincode: decoding error: Invalid character[]NEWLINE])
+cp bigfile expout
+AT_CHECK([BUILDDIR/bincode -d base64 bigfile.b64], [0], [expout])
+
+AT_CLEANUP
+
+## base32
+AT_SETUP([codec: base32])
+AT_KEYWORDS([codec base32])
+
+CODEC_TESTENCODE([base32],     [],             [])
+CODEC_TESTENCODE([base32],     [f],            [MY======])
+CODEC_TESTENCODE([base32],     [fo],           [MZXQ====])
+CODEC_TESTENCODE([base32],     [foo],          [MZXW6===])
+CODEC_TESTENCODE([base32],     [foob],         [MZXW6YQ=])
+CODEC_TESTENCODE([base32],     [fooba],        [MZXW6YTB])
+CODEC_TESTENCODE([base32],     [foobar],       [MZXW6YTBOI======])
+
+CODEC_TESTENCODE([base32hex],  [],             [])
+CODEC_TESTENCODE([base32hex],  [f],            [CO======])
+CODEC_TESTENCODE([base32hex],  [fo],           [CPNG====])
+CODEC_TESTENCODE([base32hex],  [foo],          [CPNMU===])
+CODEC_TESTENCODE([base32hex],  [foob],         [CPNMUOG=])
+CODEC_TESTENCODE([base32hex],  [fooba],        [CPNMUOJ1])
+CODEC_TESTENCODE([base32hex],  [foobar],       [CPNMUOJ1E8======])
+
+AT_CLEANUP
+
+## hex
+AT_SETUP([codec: hex])
+AT_KEYWORDS([codec hex])
+
+CODEC_TESTENCODE([hex],                [],             [])
+CODEC_TESTENCODE([hex],                [f],            [66])
+CODEC_TESTENCODE([hex],                [fo],           [666F])
+CODEC_TESTENCODE([hex],                [foo],          [666F6F])
+CODEC_TESTENCODE([hex],                [foob],         [666F6F62])
+CODEC_TESTENCODE([hex],                [fooba],        [666F6F6261])
+CODEC_TESTENCODE([hex],                [foobar],       [666F6F626172])
+
+AT_CLEANUP
+
+###----- That's all, folks --------------------------------------------------
diff --git a/codec/url.3 b/codec/url.3
new file mode 100644 (file)
index 0000000..4e8aa71
--- /dev/null
@@ -0,0 +1,148 @@
+.\" -*-nroff-*-
+.de VS
+.sp 1
+.in +5n
+.ft B
+.nf
+..
+.de VE
+.ft R
+.in -5n
+.sp 1
+.fi
+..
+.TH url 3 "20 June 1999" "Straylight/Edgeware" "mLib utilities library"
+.SH NAME
+url \- manipulation of form-urlencoded strings
+.\" @url_initenc
+.\" @url_enc
+.\" @url_initdec
+.\" @url_dec
+.SH SYNOPSIS
+.nf
+.B "#include <mLib/url.h>"
+
+.BI "void url_initenc(url_ectx *" ctx );
+.BI "void url_enc(url_ectx *" ctx ", dstr *" d ,
+.BI "             const char *" name ", const char *" value );
+
+.BI "void url_initdec(url_dctx *" ctx ", const char *" p );
+.BI "int url_dec(url_dctx *" ctx ", dstr *" n ", dstr *" v );
+.fi
+.SH DESCRIPTION
+The functions in
+.B <mLib/url.h>
+read and write `form-urlencoded' data, as specified in RFC1866.  The
+encoding represents a sequence of name/value pairs where both the name
+and value are arbitrary binary strings (although the format is optimized
+for textual data).  An encoded string contains no nonprintable
+characters or whitespace.  This interface is capable of decoding any
+urlencoded string; however, it can currently only
+.I encode
+names and values which do not contain null bytes, because the encoding
+interface uses standard C strings.
+.PP
+Encoding a sequence of name/value pairs is achieved using the
+.B url_enc
+function.  It requires as input an
+.IR "encoding context" ,
+represented as an object of type
+.BR url_ectx .
+This must be initialized before use by passing it to the function
+.BR url_initenc .
+Each call to
+.B url_enc
+encodes one name/value pair, appending the encoded output to a dynamic
+string (see
+.BR dstr (3)
+for details).
+.PP
+You can set flags in the encoding context's
+.B f
+member:
+.TP
+.B URLF_STRICT
+Be strict about escaping non-alphanumeric characters.  Without this,
+potentially unsafe characters such as
+.RB ` / '
+and
+.RB ` ~ '
+will be left unescaped, which makes encoded filenames (for example) more
+readable.
+.TP
+.B URLF_LAX
+Be very lax about non-alphanumeric characters.  Everything except
+obviously-unsafe characters like
+.RB ` & '
+and
+.RB ` = '
+are left unescaped.
+.PP
+Decoding a sequence of name/value pairs is performed using the
+.B url_dec
+function.  It requires as input a
+.IR "decoding context" ,
+represented as an object of type
+.BR url_dctx .
+This must be initialized before use by passing it to the function
+.BR url_initdec ,
+along with the address of the urlencoded string to decode.  The string
+is not modified during decoding.  Each call to
+.B url_dec
+extracts a name/value pair.  The name and value are written to the
+dynamic strings
+.I n
+and
+.IR v ,
+so you probably want to reset them before each call.  If there are no
+more name/value pairs to read,
+.B url_dec
+returns zero; otherwise it returns a nonzero value.
+.SH EXAMPLE
+The example code below demonstrates converting between a symbol table
+and a urlencoded representation.  The code is untested.
+.VS
+#include <stdlib.h>
+#include <mLib/alloc.h>
+#include <mLib/dstr.h>
+#include <mLib/sym.h>
+#include <mLib/url.h>
+
+typedef struct {
+  sym_base _b;
+  char *v;
+} val;
+
+void decode(sym_table *t, const char *p)
+{
+  url_dctx c;
+  dstr n = DSTR_INIT, v = DSTR_INIT;
+
+  for (url_initdec(&c, p); url_dec(&c, &n, &v); ) {
+    unsigned f;
+    val *vv = sym_find(t, n.buf, -1, sizeof(*vv), &f);
+    if (f)
+      free(vv->v);
+    vv->v = xstrdup(v.buf);
+    DRESET(&n);
+    DRESET(&v);
+  }
+  dstr_destroy(&n);
+  dstr_destroy(&v);
+}
+
+void encode(sym_table *t, dstr *d)
+{
+  sym_iter i;
+  url_ectx c;
+  val *v;
+
+  url_initenc(&c);
+  for (sym_mkiter(&i, t); (v = sym_next(&i)) != 0; )
+    url_enc(&c, d, SYM_NAME(v), v->v);
+}
+.VE
+.SH "SEE ALSO"
+.BR mLib (3).
+.SH AUTHOR
+Mark Wooding, <mdw@distorted.org.uk>.
diff --git a/codec/url.c b/codec/url.c
new file mode 100644 (file)
index 0000000..eecfe82
--- /dev/null
@@ -0,0 +1,205 @@
+/* -*-c-*-
+ *
+ * Parsing and construction of url-encoded name/value pairs
+ *
+ * (c) 1999 Straylight/Edgeware
+ */
+
+/*----- Licensing notice --------------------------------------------------*
+ *
+ * This file is part of the mLib utilities library.
+ *
+ * mLib is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * mLib is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with mLib; if not, write to the Free
+ * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+ * MA 02111-1307, USA.
+ */
+
+/*----- Header files ------------------------------------------------------*/
+
+#include <ctype.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "dstr.h"
+#include "macros.h"
+#include "url.h"
+
+/*----- Main code ---------------------------------------------------------*/
+
+/* --- @url_initenc@ --- *
+ *
+ * Arguments:  @url_ectx *ctx@ = pointer to context block
+ *
+ * Returns:    ---
+ *
+ * Use:                Initializes a URL encoding context.
+ */
+
+void url_initenc(url_ectx *ctx) { ctx->f = 0; }
+
+/* --- @encode@ --- *
+ *
+ * Arguments:  @url_ectx *ctx@ = encoding context
+ *             @dstr *d@ = pointer to output string
+ *             @const char *p@ = pointer to thing to encode
+ *
+ * Returns:    ---
+ *
+ * Use:                Encodes the input string into the output string.
+ */
+
+static void encode(url_ectx *ctx, dstr *d, const char *p)
+{
+  while (*p) {
+    switch (*p) {
+      case ' ': DPUTC(d, '+');
+       break;
+      default:
+       if (ISSPACE(*p)) goto unsafe;
+       else if (ISALNUM(*p)) goto safe;
+       else if (ctx->f&URLF_LAX) goto safe;
+       else goto unsafe;
+      case '/': case '~':
+       if (ctx->f&URLF_STRICT) goto unsafe; /* else fall through... */
+      safe: case '-': case '.': case '_':
+       DPUTC(d, *p);   break;
+      unsafe: case '+': case '%': case '=': case '&': case ';':
+       dstr_putf(d, "%%%02x", *p); break;
+    }
+    p++;
+  }
+}
+
+/* --- @url_enc@ --- *
+ *
+ * Arguments:  @url_ectx *ctx@ = pointer to encoding context
+ *             @dstr *d@ = pointer to output string
+ *             @const char *name@ = pointer to name
+ *             @const char *value@ = pointer to value
+ *
+ * Returns:    ---
+ *
+ * Use:                Writes an assignment between @name@ and @value@ to the
+ *             output string, encoding the values properly.
+ */
+
+void url_enc(url_ectx *ctx, dstr *d, const char *name, const char *value)
+{
+  if (ctx->f & URLF_SEP)
+    DPUTC(d, (ctx->f & URLF_SEMI) ? ';' : '&');
+  encode(ctx, d, name);
+  DPUTC(d, '=');
+  encode(ctx, d, value);
+  DPUTZ(d);
+  ctx->f |= URLF_SEP;
+}
+
+/* --- @url_initdec@ --- *
+ *
+ * Arguments:  @url_dctx *ctx@ = pointer to context block
+ *             @const char *p@ = string to read data from
+ *
+ * Returns:    ---
+ *
+ * Use:                Initializes a URL decoding context.
+ */
+
+void url_initdec(url_dctx *ctx, const char *p) { ctx->p = p; ctx->f = 0; }
+
+/* --- @decode@ --- *
+ *
+ * Arguments:  @url_dctx *ctx@ = pointer to the context
+ *             @dstr *d@ = pointer to output string
+ *             @const char *p@ = pointer to input data
+ *             @int eq@ = whether to stop at `=' characters
+ *
+ * Returns:    Pointer to next available character.
+ *
+ * Use:                Does a URL decode.
+ */
+
+static const char *decode(url_dctx *ctx, dstr *d, const char *p, int eq)
+{
+  if (!*p)
+    return (0);
+  for (;;) {
+    switch (*p) {
+      case '=':
+       if (eq)
+         return (p);
+       goto boring;
+      case ';':
+       if (ctx->f & URLF_SEMI)
+         return (p);
+       goto boring;
+      case 0:
+      case '&':
+       return (p);
+      case '+':
+       DPUTC(d, ' ');
+       break;
+      case '%': {
+       unsigned int ch;
+       int n;
+       int x = sscanf(p + 1, "%2x%n", &ch, &n);
+       if (x == 1) {
+         DPUTC(d, ch);
+         p += n;
+         break;
+       }
+      }
+      default:
+      boring:
+       DPUTC(d, *p);
+       break;
+    }
+    p++;
+  }
+}
+
+/* --- @url_dec@ --- *
+ *
+ * Arguments:  @url_dctx *ctx@ = pointer to decode context
+ *             @dstr *n@ = pointer to output string for name
+ *             @dstr *v@ = pointer to output string for value
+ *
+ * Returns:    Nonzero if it read something, zero if there's nothing left
+ *
+ * Use:                Decodes the next name/value pair from a urlencoded string.
+ */
+
+int url_dec(url_dctx *ctx, dstr *n, dstr *v)
+{
+  const char *p = ctx->p;
+  size_t l = n->len;
+
+again:
+  if ((p = decode(ctx, n, p, 1)) == 0 || *p == 0)
+    return (0);
+  if (*p != '=') {
+    p++;
+    n->len = l;
+    goto again;
+  }
+  p++;
+  if ((p = decode(ctx, v, p, 0)) == 0)
+    return (0);
+  DPUTZ(n);
+  DPUTZ(v);
+  ctx->p = p;
+  return (1);
+}
+
+/*----- That's all, folks -------------------------------------------------*/
diff --git a/codec/url.h b/codec/url.h
new file mode 100644 (file)
index 0000000..0e9e951
--- /dev/null
@@ -0,0 +1,118 @@
+/* -*-c-*-
+ *
+ * Parsing and construction of url-encoded name/value pairs
+ *
+ * (c) 1999 Straylight/Edgeware
+ */
+
+/*----- Licensing notice --------------------------------------------------*
+ *
+ * This file is part of the mLib utilities library.
+ *
+ * mLib is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * mLib is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with mLib; if not, write to the Free
+ * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+ * MA 02111-1307, USA.
+ */
+
+#ifndef MLIB_URL_H
+#define MLIB_URL_H
+
+#ifdef __cplusplus
+  extern "C" {
+#endif
+
+/*----- Header files ------------------------------------------------------*/
+
+#ifndef MLIB_DSTR_H
+#  include "dstr.h"
+#endif
+
+/*----- Data structures ---------------------------------------------------*/
+
+typedef struct url_ectx {
+  unsigned f;
+} url_ectx;
+
+
+typedef struct url_dctx {
+  const char *p;
+  unsigned f;
+} url_dctx;
+
+#define URLF_SEP 1u
+#define URLF_STRICT 2u
+#define URLF_LAX 4u
+#define URLF_SEMI 8u
+
+/*----- Functions provided ------------------------------------------------*/
+
+/* --- @url_initenc@ --- *
+ *
+ * Arguments:  @url_ectx *ctx@ = pointer to context block
+ *
+ * Returns:    ---
+ *
+ * Use:                Initializes a URL encoding context.
+ */
+
+extern void url_initenc(url_ectx */*ctx*/);
+
+/* --- @url_enc@ --- *
+ *
+ * Arguments:  @url_ectx *ctx@ = pointer to encoding context
+ *             @dstr *d@ = pointer to output string
+ *             @const char *name@ = pointer to name
+ *             @const char *value@ = pointer to value
+ *
+ * Returns:    ---
+ *
+ * Use:                Writes an assignment between @name@ and @value@ to the
+ *             output string, encoding the values properly.
+ */
+
+extern void url_enc(url_ectx */*ctx*/, dstr */*d*/,
+                   const char */*name*/, const char */*value*/);
+
+/* --- @url_initdec@ --- *
+ *
+ * Arguments:  @url_dctx *ctx@ = pointer to context block
+ *             @const char *p@ = string to read data from
+ *
+ * Returns:    ---
+ *
+ * Use:                Initializes a URL decoding context.
+ */
+
+extern void url_initdec(url_dctx */*ctx*/, const char */*p*/);
+
+/* --- @url_dec@ --- *
+ *
+ * Arguments:  @url_dctx *ctx@ = pointer to decode context
+ *             @dstr *n@ = pointer to output string for name
+ *             @dstr *v@ = pointer to output string for value
+ *
+ * Returns:    Nonzero if it read something, zero if there's nothing left
+ *
+ * Use:                Decodes the next name/value pair from a urlencoded string.
+ */
+
+extern int url_dec(url_dctx */*ctx*/, dstr */*n*/, dstr */*v*/);
+
+/*----- That's all, folks -------------------------------------------------*/
+
+#ifdef __cplusplus
+  }
+#endif
+
+#endif
diff --git a/configure.ac b/configure.ac
new file mode 100644 (file)
index 0000000..60f82b9
--- /dev/null
@@ -0,0 +1,150 @@
+dnl -*-autoconf-*-
+dnl
+dnl Configuration script for mLib
+dnl
+dnl (c) 2008 Straylight/Edgeware
+dnl
+
+dnl----- Licensing notice ---------------------------------------------------
+dnl
+dnl This file is part of the mLib utilities library.
+dnl
+dnl mLib is free software; you can redistribute it and/or modify
+dnl it under the terms of the GNU Library General Public License as
+dnl published by the Free Software Foundation; either version 2 of the
+dnl License, or (at your option) any later version.
+dnl
+dnl mLib is distributed in the hope that it will be useful,
+dnl but WITHOUT ANY WARRANTY; without even the implied warranty of
+dnl MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+dnl GNU Library General Public License for more details.
+dnl
+dnl You should have received a copy of the GNU Library General Public
+dnl License along with mLib; if not, write to the Free
+dnl Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+dnl MA 02111-1307, USA.
+
+dnl--------------------------------------------------------------------------
+dnl Initialization.
+
+mdw_AUTO_VERSION
+AC_INIT([mLib], AUTO_VERSION, [mdw@distorted.org.uk], [mLib])
+AC_CONFIG_SRCDIR([mLib.pc.in])
+AC_CONFIG_AUX_DIR([config])
+AM_INIT_AUTOMAKE([foreign subdir-objects])
+mdw_SILENT_RULES
+
+AC_PROG_CC
+AM_PROG_CC_C_O
+AM_PROG_LIBTOOL
+AX_CFLAGS_WARN_ALL
+mdw_LIBTOOL_VERSION_INFO
+
+AC_CHECK_PROGS([AUTOM4TE], [autom4te])
+
+mdw_MANEXT([mLib])
+
+AC_DEFINE_UNQUOTED([SRCDIR], ["$(cd $srcdir && pwd)"],
+                  [absolute pathname for the source directory.])
+
+dnl--------------------------------------------------------------------------
+dnl C programming environment.
+
+MLIB_LIBS=
+
+dnl Headers.
+AC_CHECK_HEADERS([float.h])
+AC_CHECK_HEADERS([stdint.h])
+
+dnl Libraries.
+mdw_ORIG_LIBS=$LIBS LIBS=$MLIB_LIBS
+AC_SEARCH_LIBS([socket], [socket])
+AC_SEARCH_LIBS([gethostbyname], [nsl resolv])
+MLIB_LIBS=$LIBS LIBS=$mdw_ORIG_LIBS
+
+dnl Functions.
+AC_CHECK_FUNCS([snprintf])
+
+dnl Types.
+AC_CHECK_TYPE([socklen_t], [],
+  [AC_DEFINE([socklen_t], [int],
+     [Define to `int' if <sys/socket.h> does not define])],
+  [AC_INCLUDES_DEFAULT
+#include <sys/socket.h>
+])
+
+dnl Which version of struct msghdr do we have?
+AC_CHECK_MEMBERS([struct msgdr.msg_control],,, [
+#include <sys/types.h>
+#include <sys/socket.h>
+])
+
+dnl Find out whether we're cross-compiling.
+AM_CONDITIONAL([CROSS_COMPILING], [test "$cross_compiling" = yes])
+
+dnl Set the master library list.
+AC_SUBST([MLIB_LIBS])
+
+dnl--------------------------------------------------------------------------
+dnl Name resolution.
+
+AC_ARG_WITH([adns],
+  AS_HELP_STRING([--with-adns],
+                [use ADNS library for background name resolution]),
+  [want_adns=$withval],
+  [want_adns=auto])
+
+mdw_ORIG_LIBS=$LIBS LIBS=$MLIB_LIBS
+case $want_adns in
+  no) ;;
+  *) AC_SEARCH_LIBS([adns_init], [adns], [have_adns=yes], [have_adns=no]) ;;
+esac
+MLIB_LIBS=$LIBS LIBS=$mdw_ORIG_LIBS
+case $want_adns,$have_adns in
+  yes,no)
+    AC_MSG_ERROR([ADNS library not found but explicitly requested])
+    ;;
+  yes,yes | auto,yes)
+    use_adns=yes
+    AC_DEFINE([HAVE_ADNS], [1],
+             [define if you have (and want to use) the ADNS library.])
+    ;;
+  no,* | auto,no)
+    use_adns=no
+    mdw_DEFINE_PATHS([
+      AC_DEFINE_UNQUOTED([BRES_SERVER],
+                        ["mdw_PATH($libexecdir)/$PACKAGE/mdw_PROG(bres)"],
+                        [pathname to the standalone `bres' binary.'])
+    ])
+    ;;
+esac
+AM_CONDITIONAL([WITH_ADNS], [test "$use_adns" = yes])
+
+dnl--------------------------------------------------------------------------
+dnl Python (used for testing).
+
+AM_PATH_PYTHON([2.4],, [:])
+
+dnl--------------------------------------------------------------------------
+dnl Output.
+
+AC_CONFIG_HEADER([config/config.h])
+AC_CONFIG_TESTDIR([t])
+
+AC_CONFIG_FILES(
+  [Makefile]
+  [buf/Makefile]
+  [codec/Makefile]
+  [hash/Makefile]
+  [mem/Makefile]
+  [sel/Makefile]
+  [struct/Makefile]
+  [sys/Makefile]
+  [test/Makefile]
+  [trace/Makefile]
+  [ui/Makefile]
+  [utils/Makefile]
+  [t/Makefile t/atlocal])
+AC_OUTPUT
+
+dnl------ That's all, folks -------------------------------------------------
diff --git a/debian/.gitignore b/debian/.gitignore
new file mode 100644 (file)
index 0000000..7fb2508
--- /dev/null
@@ -0,0 +1,19 @@
+## General stuff.
+/build-*/
+/files
+/substvars
+/tmp/
+/tmp-*/
+/*.debhelper
+/*.substvars
+/stamp-*
+/*.log
+
+## Individual packages.
+/mlib2/
+/mlib2-adns/
+/mlib-bin/
+/mlib-dev/
+
+## Other debris.
+/mlib2-adns.install
diff --git a/debian/changelog b/debian/changelog
new file mode 100644 (file)
index 0000000..a2fc30a
--- /dev/null
@@ -0,0 +1,213 @@
+mlib (2.5.0) experimental; urgency=medium
+
+  * Fix internal Python build scripts to work with Python 3.
+  * Introduce wrapper macros around the <ctype.h> functions (to handle
+    negative character codes correctly), and the <string.h> comparison
+    functions `strcmp', `strncmp', and `memcmp'.
+
+ -- Mark Wooding <mdw@distorted.org.uk>  Fri, 08 May 2020 20:16:37 +0100
+
+mlib (2.4.2) experimental; urgency=medium
+
+  * mlib (buf): Handle 64-bit length prefixes correctly on 32-bit
+    platforms.  Previously, a huge length was reduced modulo 2^32 prior to
+    checking via an implicit conversion to `size_t'.
+  * mlib (bres-adns): Use correct array bound when reassembling `hostent'
+    structures to return.  Apparently this has never been correct.
+  * mlib-dev: Some formatting fixes in manpages.
+
+ -- Mark Wooding <mdw@distorted.org.uk>  Fri, 08 May 2020 13:43:00 +0100
+
+mlib (2.4.1) experimental; urgency=medium
+
+  * (internals): Delete `track', which stopped being built or distributed
+    in 2.2.0 and nobody noticed.
+  * mlib-dev: Add a `STATIC_ASSERT' macro, because we've wanted one for ages.
+  * debian: Ship a shared-library `symbols' file for more precise
+    dependencies.
+
+ -- Mark Wooding <mdw@distorted.org.uk>  Sun, 29 Sep 2019 15:13:30 +0100
+
+mlib (2.4.0) experimental; urgency=medium
+
+  * mlib (crc32): Make table be read-only.
+  * mlib-dev (macros): Decorate attribute names with `__...__'.
+  * mlib-bin (crc-mktab, unihash-mkstatic): Add option for generating
+    `const' tables.
+  * Support building with Clang.
+
+ -- Mark Wooding <mdw@distorted.org.uk>  Sat, 21 Sep 2019 21:37:47 +0100
+
+mlib (2.3.3.1) experimental; urgency=medium
+
+  * macros: Add missing fallback definition for `NORETURN'.
+
+ -- Mark Wooding <mdw@distorted.org.uk>  Fri, 09 Aug 2019 11:32:38 +0100
+
+mlib (2.3.3) experimental; urgency=medium
+
+  * mlib2-adns: Make sure there's a `DT_NEEDED' entry for libadns.  I
+    don't know why this has just become important.
+  * mlib-dev: Fix the `pkg-config' snippet so that static linking works.
+  * macros: Properly parenthesize the `N' macro's argument.  (I think it
+    doesn't make any difference.)
+  * fdpass: Fix file-descriptor passing on 64-bit targets.  This was
+    broken by an embarrassing typo (and inadequate testing).
+
+ -- Mark Wooding <mdw@distorted.org.uk>  Tue, 25 Dec 2018 15:06:00 +0000
+
+mlib (2.3.2) experimental; urgency=medium
+
+  * url: Fix crash in `url_enc' introduced in earlier alleged fix.
+
+ -- Mark Wooding <mdw@distorted.org.uk>  Tue, 12 Jun 2018 10:53:19 +0100
+
+mlib (2.3.1) experimental; urgency=medium
+
+  * Pick up missed fix from 2.2.5.
+
+ -- Mark Wooding <mdw@distorted.org.uk>  Tue, 12 Jun 2018 01:40:14 +0100
+
+mlib (2.3.0) experimental; urgency=medium
+
+  * url: Always encode whitespace characters.  Particularly egregiously,
+    mLib used to leave linefeeds unescaped in `lax' mode.
+  * bits: Document the many improvements since the original version nearly
+    20 years ago.
+  * bits: Add macros for byte-swapping integers in-place.
+  * bits: Use compiler intrinsics where available.
+
+ -- Mark Wooding <mdw@distorted.org.uk>  Tue, 12 Jun 2018 00:30:47 +0100
+
+mlib (2.2.5) experimental; urgency=medium
+
+  * ident: Only close the socket once if connection fails early.
+
+ -- Mark Wooding <mdw@distorted.org.uk>  Sat, 09 Jun 2018 17:40:21 +0100
+
+mlib (2.2.4) experimental; urgency=medium
+
+  * debian: Update for Debhelper 9.
+  * debian: Multi-arch support.
+  * mlib-dev: Compiler-hack support for Clang.
+
+ -- Mark Wooding <mdw@distorted.org.uk>  Sun, 13 Aug 2017 20:12:19 +0100
+
+mlib (2.2.3) experimental; urgency=low
+
+  * build: Cope with newer Autotools and related equipment.
+  * Miscellaneous small fixes for Cygwin.
+
+ -- Mark Wooding <mdw@distorted.org.uk>  Sat, 04 Jun 2016 01:09:53 +0100
+
+mlib (2.2.2.2) experimental; urgency=low
+
+  * codec: Minor formatting fix to manpage.
+  * Fixed Build-Depends and missing debian/source/format file.
+
+ -- Mark Wooding <mdw@distorted.org.uk>  Thu, 18 Feb 2016 08:19:10 +0000
+
+mlib (2.2.2.1) experimental; urgency=low
+
+  * dstr: `dstr_putf' no longer leaks memory.  Sorry.
+
+ -- Mark Wooding <mdw@distorted.org.uk>  Sun, 18 Jan 2015 16:54:30 +0000
+
+mlib (2.2.2) experimental; urgency=low
+
+  * bres: Fixed `bres_abort' scrambing the freelist leading to a segfault.
+  * codec: New flag `CDCF_IGNSPC' to ignore whitespace when decoding.  This
+    is the default in bincode(1).
+  * dstr: `dstr_putf' no longer crashes on `%.*s' and similar.
+  * dstr: `dstr_putf' now understands `%n$...' positional placeholders.
+
+ -- Mark Wooding <mdw@distorted.org.uk>  Sun, 20 Jul 2014 21:35:20 +0100
+
+mlib (2.2.1) experimental; urgency=low
+
+  * crc32: Fix on 64-bit systems.
+  * dstr: `dstr_putc' now takes an `int' argument.
+  * macros.h: Provide macros for annotating functions (e.g., as following
+    various kinds of varargs protocols) and controlling warnings.
+
+ -- Mark Wooding <mdw@distorted.org.uk>  Fri, 28 Jun 2013 23:27:49 +0100
+
+mlib (2.2.0.1) experimental; urgency=low
+
+  * Brown paper-bag: failed to include `codec.h' in `hex.h' and `base32.h'
+    leading to build failures of clients.
+
+ -- Mark Wooding <mdw@distorted.org.uk>  Sat, 12 Jan 2013 18:49:36 +0000
+
+mlib (2.2.0) experimental; urgency=low
+
+  * Major internal reorganization.
+  * Ship precomputed tables and provide partial support for
+    cross-compilation.
+  * Overhaul of binary-to-text coding: now has better error handling, and
+    a single unified engine.  Also provides a program, bincode(1), for
+    exercising the system.
+  * Replace random ad-hoc testing by a system based on GNU Autotest.
+
+ -- Mark Wooding <mdw@distorted.org.uk>  Sun, 03 May 2009 01:44:45 +0100
+
+mlib (2.1.1) experimental; urgency=low
+
+  * Actually declare buf_putstr* in buf.h.
+  * Skip past terminating null in buf_get*z, rather than leaving it for
+    the next get.
+
+ -- Mark Wooding <mdw@distorted.org.uk>  Sat, 05 May 2012 13:18:40 +0100
+
+mlib (2.1.0) experimental; urgency=low
+
+  * New function `mdup' for renumbering file descriptors.
+  * Various internal build-system changes.
+
+ -- Mark Wooding <mdw@distorted.org.uk>  Sun, 04 Jan 2009 14:45:07 +0000
+
+mlib (2.0.7) experimental; urgency=low
+
+  * The new build system put the headers in /usr/include/mlib, where
+    nothing could find them.  Put them back in /usr/include/mLib where
+    they belong.
+  * Actually include config.h in the correct places, so that, for example,
+    dstr_putf doesn't explode.
+
+ -- Mark Wooding <mdw@distorted.org.uk>  Sun, 28 Dec 2008 21:23:59 +0000
+
+mlib (2.0.6) experimental; urgency=low
+
+  * Build system overhaul.
+
+ -- Mark Wooding <mdw@distorted.org.uk>  Sat, 27 Dec 2008 13:42:39 +0000
+
+mlib (2.0.5) experimental; urgency=low
+
+  * Fix versioncmp to deal with `~' characters properly, in line with
+    Debian policy.
+
+ -- Mark Wooding <mdw@distorted.org.uk>  Wed, 14 May 2008 15:10:32 +0100
+
+mlib (2.0.4) experimental; urgency=low
+
+  * Switch over to pkgconfig.  This is largely a stopgap release, to stop
+    stuff breaking hopelessly while mLib 3 is worked on.
+
+ -- Mark Wooding <mdw@distorted.org.uk>  Mon, 17 Mar 2008 17:51:45 +0000
+
+mlib (2.0.3) experimental; urgency=low
+
+  * Document hex encoding/decoding.
+  * Add file descriptor passing.
+  * Add ADNS-based background resolver.
+  * Split binaries off into their own package.
+  * Document, fix and test universal hashing; use it in symbol tables.
+
+ -- Mark Wooding <mdw@nsict.org>  Mon, 15 Dec 2003 20:54:16 +0000
+
+mlib (2.0.2) experimental; urgency=low
+
+  * Debianization!
+
+ -- Mark Wooding <mdw@nsict.org>  Sat, 8 Nov 2003 22:43:10 +0000
diff --git a/debian/clean b/debian/clean
new file mode 100644 (file)
index 0000000..7825f92
--- /dev/null
@@ -0,0 +1 @@
+tmp-adns
diff --git a/debian/common.symbols b/debian/common.symbols
new file mode 100644 (file)
index 0000000..48325cf
--- /dev/null
@@ -0,0 +1,473 @@
+### -*-conf-*-
+
+libmLib.so.2 mlib2 #MINVER# | mlib2-adns #MINVER#
+* Build-Depends-Package: mlib-dev
+
+###--------------------------------------------------------------------------
+### Utilities.
+
+## exc
+       exc_uncaught@Base 2.0.7
+       __exc_list@Base 2.0.7
+       __exc_rethrow@Base 2.0.7
+       __exc_throw@Base 2.0.7
+
+## str
+       str_getword@Base 2.0.7
+       str_match@Base 2.0.7
+       str_matchx@Base 2.0.7
+       str_split@Base 2.0.7
+       str_qword@Base 2.0.7
+       str_qsplit@Base 2.0.7
+       str_sanitize@Base 2.0.7
+
+## versioncmp
+       versioncmp@Base 2.0.7
+
+###--------------------------------------------------------------------------
+### Memory allocation.
+
+## arena
+       a_alloc@Base 2.0.7
+       a_free@Base 2.0.7
+       a_realloc@Base 2.0.7
+       arena_fakerealloc@Base 2.0.7
+       arena_global@Base 2.0.7
+       arena_stdlib@Base 2.0.7
+
+## alloc
+       x_alloc@Base 2.0.7
+       x_free@Base 2.0.7
+       x_realloc@Base 2.0.7
+       x_strdup@Base 2.0.7
+       xmalloc@Base 2.0.7
+       xrealloc@Base 2.0.7
+       xfree@Base 2.0.7
+       xstrdup@Base 2.0.7
+
+## sub
+       subarena_create@Base 2.0.7
+       subarena_destroy@Base 2.0.7
+       subarena_alloc@Base 2.0.7
+       subarena_free@Base 2.0.7
+       sub_init@Base 2.0.7
+       sub_alloc@Base 2.0.7
+       sub_free@Base 2.0.7
+       sub_global@Base 2.0.7
+
+## pool
+       pool_init@Base 2.0.7
+       pool_create@Base 2.0.7
+       pool_sub@Base 2.0.7
+       pool_destroy@Base 2.0.7
+       pool_add@Base 2.0.7
+       pool_alloc@Base 2.0.7
+       pool_strdup@Base 2.0.7
+       pool_fopen@Base 2.0.7
+       pool_fclose@Base 2.0.7
+       pool_subarena@Base 2.0.7
+
+###--------------------------------------------------------------------------
+### User interface.
+
+## mdwopt
+       mdwopt@Base 2.0.7
+       mdwopt_global@Base 2.0.7
+
+## quis
+       ego@Base 2.0.7
+       quis@Base 2.0.7
+       pquis@Base 2.0.7
+       (optional=internal)pn__name@Base 2.0.7
+
+## report
+       die@Base 2.0.7
+       moan@Base 2.0.7
+
+###--------------------------------------------------------------------------
+### Hashing.
+
+## crc32
+       crc32@Base 2.2.1
+       crc32_table@Base 2.0.7
+
+## unihash
+       unihash@Base 2.0.7
+       unihash_hash@Base 2.0.7
+       unihash_setkey@Base 2.0.7
+       unihash_global@Base 2.0.7
+
+###--------------------------------------------------------------------------
+### Data structures.
+
+## dstr
+       dstr_create@Base 2.0.7
+       dstr_destroy@Base 2.0.7
+       dstr_ensure@Base 2.0.7
+       dstr_reset@Base 2.0.7
+       dstr_tidy@Base 2.0.7
+       dstr_putc@Base 2.2.1
+       dstr_putm@Base 2.0.7
+       dstr_puts@Base 2.0.7
+       dstr_putz@Base 2.0.7
+       dstr_putd@Base 2.0.7
+       dstr_putf@Base 2.2.2.1
+       dstr_vputf@Base 2.0.7
+       dstr_putline@Base 2.0.7
+       dstr_write@Base 2.0.7
+
+## dspool
+       dspool_create@Base 2.0.7
+       dspool_destroy@Base 2.0.7
+       dspool_get@Base 2.0.7
+       dspool_put@Base 2.0.7
+
+## buf
+       buf_init@Base 2.0.7
+       buf_ensure@Base 2.0.7
+       buf_break@Base 2.0.7
+       buf_flip@Base 2.0.7
+       buf_get@Base 2.0.7
+       buf_getbyte@Base 2.0.7
+       buf_getu8@Base 2.0.7
+       buf_getu16@Base 2.0.7
+       buf_getu16b@Base 2.0.7
+       buf_getu16l@Base 2.0.7
+       buf_getu24@Base 2.0.7
+       buf_getu24b@Base 2.0.7
+       buf_getu24l@Base 2.0.7
+       buf_getu32@Base 2.0.7
+       buf_getu32b@Base 2.0.7
+       buf_getu32l@Base 2.0.7
+       buf_getu64@Base 2.0.7
+       buf_getu64b@Base 2.0.7
+       buf_getu64l@Base 2.0.7
+       buf_getbuf8@Base 2.0.7
+       buf_getbuf16@Base 2.0.7
+       buf_getbuf16b@Base 2.0.7
+       buf_getbuf16l@Base 2.0.7
+       buf_getbuf24@Base 2.0.7
+       buf_getbuf24b@Base 2.0.7
+       buf_getbuf24l@Base 2.0.7
+       buf_getbuf32@Base 2.0.7
+       buf_getbuf32b@Base 2.0.7
+       buf_getbuf32l@Base 2.0.7
+       buf_getbuf64@Base 2.0.7
+       buf_getbuf64b@Base 2.0.7
+       buf_getbuf64l@Base 2.0.7
+       buf_getbufz@Base 2.1.1
+       buf_getdstr8@Base 2.0.7
+       buf_getdstr16@Base 2.0.7
+       buf_getdstr16b@Base 2.0.7
+       buf_getdstr16l@Base 2.0.7
+       buf_getdstr24@Base 2.0.7
+       buf_getdstr24b@Base 2.0.7
+       buf_getdstr24l@Base 2.0.7
+       buf_getdstr32@Base 2.0.7
+       buf_getdstr32b@Base 2.0.7
+       buf_getdstr32l@Base 2.0.7
+       buf_getdstr64@Base 2.0.7
+       buf_getdstr64b@Base 2.0.7
+       buf_getdstr64l@Base 2.0.7
+       buf_getdstrz@Base 2.1.1
+       buf_getmem8@Base 2.0.7
+       buf_getmem16@Base 2.0.7
+       buf_getmem16b@Base 2.0.7
+       buf_getmem16l@Base 2.0.7
+       buf_getmem24@Base 2.0.7
+       buf_getmem24b@Base 2.0.7
+       buf_getmem24l@Base 2.0.7
+       buf_getmem32@Base 2.0.7
+       buf_getmem32b@Base 2.0.7
+       buf_getmem32l@Base 2.0.7
+       buf_getmem64@Base 2.0.7
+       buf_getmem64b@Base 2.0.7
+       buf_getmem64l@Base 2.0.7
+       buf_getmemz@Base 2.1.1
+       buf_put@Base 2.0.7
+       buf_putbyte@Base 2.0.7
+       buf_putu8@Base 2.0.7
+       buf_putu16@Base 2.0.7
+       buf_putu16b@Base 2.0.7
+       buf_putu16l@Base 2.0.7
+       buf_putu24@Base 2.0.7
+       buf_putu24b@Base 2.0.7
+       buf_putu24l@Base 2.0.7
+       buf_putu32@Base 2.0.7
+       buf_putu32b@Base 2.0.7
+       buf_putu32l@Base 2.0.7
+       buf_putu64@Base 2.0.7
+       buf_putu64b@Base 2.0.7
+       buf_putu64l@Base 2.0.7
+       buf_putbuf16@Base 2.0.7
+       buf_putbuf16b@Base 2.0.7
+       buf_putbuf16l@Base 2.0.7
+       buf_putbuf24@Base 2.0.7
+       buf_putbuf24b@Base 2.0.7
+       buf_putbuf24l@Base 2.0.7
+       buf_putbuf32@Base 2.0.7
+       buf_putbuf32b@Base 2.0.7
+       buf_putbuf32l@Base 2.0.7
+       buf_putbuf64@Base 2.0.7
+       buf_putbuf64b@Base 2.0.7
+       buf_putbuf64l@Base 2.0.7
+       buf_putbuf8@Base 2.0.7
+       buf_putbufz@Base 2.0.7
+       buf_putdstr8@Base 2.0.7
+       buf_putdstr16@Base 2.0.7
+       buf_putdstr16b@Base 2.0.7
+       buf_putdstr16l@Base 2.0.7
+       buf_putdstr24@Base 2.0.7
+       buf_putdstr24b@Base 2.0.7
+       buf_putdstr24l@Base 2.0.7
+       buf_putdstr32@Base 2.0.7
+       buf_putdstr32b@Base 2.0.7
+       buf_putdstr32l@Base 2.0.7
+       buf_putdstr64@Base 2.0.7
+       buf_putdstr64b@Base 2.0.7
+       buf_putdstr64l@Base 2.0.7
+       buf_putdstrz@Base 2.0.7
+       buf_putmem8@Base 2.0.7
+       buf_putmem16@Base 2.0.7
+       buf_putmem16b@Base 2.0.7
+       buf_putmem16l@Base 2.0.7
+       buf_putmem24@Base 2.0.7
+       buf_putmem24b@Base 2.0.7
+       buf_putmem24l@Base 2.0.7
+       buf_putmem32@Base 2.0.7
+       buf_putmem32b@Base 2.0.7
+       buf_putmem32l@Base 2.0.7
+       buf_putmem64@Base 2.0.7
+       buf_putmem64b@Base 2.0.7
+       buf_putmem64l@Base 2.0.7
+       buf_putmemz@Base 2.0.7
+       buf_putstr8@Base 2.0.7
+       buf_putstr16@Base 2.0.7
+       buf_putstr16b@Base 2.0.7
+       buf_putstr16l@Base 2.0.7
+       buf_putstr24@Base 2.0.7
+       buf_putstr24b@Base 2.0.7
+       buf_putstr24l@Base 2.0.7
+       buf_putstr32@Base 2.0.7
+       buf_putstr32b@Base 2.0.7
+       buf_putstr32l@Base 2.0.7
+       buf_putstr64@Base 2.0.7
+       buf_putstr64b@Base 2.0.7
+       buf_putstr64l@Base 2.0.7
+       buf_putstrz@Base 2.0.7
+
+## darray
+       da_ensure@Base 2.0.7
+       da_shunt@Base 2.0.7
+       da_tidy@Base 2.0.7
+
+## hash
+       hash_bin@Base 2.0.7
+       hash_create@Base 2.0.7
+       hash_destroy@Base 2.0.7
+       hash_extend@Base 2.0.7
+       hash_mkiter@Base 2.0.7
+       hash_next@Base 2.0.7
+       hash_remove@Base 2.0.7
+
+## sym
+       sym_create@Base 2.0.7
+       sym_destroy@Base 2.0.7
+       sym_find@Base 2.0.7
+       sym_remove@Base 2.0.7
+       sym_mkiter@Base 2.0.7
+       sym_next@Base 2.0.7
+
+## atom
+       atom_createtable@Base 2.0.7
+       atom_destroytable@Base 2.0.7
+       atom_intern@Base 2.0.7
+       atom_nintern@Base 2.0.7
+       atom_gensym@Base 2.0.7
+       atom_name@Base 2.0.7
+       atom_len@Base 2.0.7
+       atom_hash@Base 2.0.7
+       atom_mkiter@Base 2.0.7
+       atom_next@Base 2.0.7
+
+## assoc
+       assoc_create@Base 2.0.7
+       assoc_destroy@Base 2.0.7
+       assoc_find@Base 2.0.7
+       assoc_remove@Base 2.0.7
+       assoc_mkiter@Base 2.0.7
+       assoc_next@Base 2.0.7
+
+###--------------------------------------------------------------------------
+### Encoding and decoding.
+
+## codec
+       codec_strerror@Base 2.2.0
+       base32_class@Base 2.2.0
+       base32hex_class@Base 2.2.0
+       base64_class@Base 2.2.0
+       file64_class@Base 2.2.0
+       base64url_class@Base 2.2.0
+       hex_class@Base 2.2.0
+       null_codec_class@Base 2.2.0
+
+## base32
+       base32_init@Base 2.0.7
+       base32_encode@Base 2.0.7
+       base32_decode@Base 2.0.7
+
+## base64
+       base64_init@Base 2.0.7
+       base64_encode@Base 2.0.7
+       base64_decode@Base 2.0.7
+
+## hex
+       hex_init@Base 2.0.7
+       hex_encode@Base 2.0.7
+       hex_decode@Base 2.0.7
+
+## url
+       url_initenc@Base 2.0.7
+       url_enc@Base 2.3.2
+       url_initdec@Base 2.0.7
+       url_dec@Base 2.0.7
+
+###--------------------------------------------------------------------------
+### System utilities.
+
+## daemonize
+       daemonize@Base 2.0.7
+       detachtty@Base 2.0.7
+
+## env
+       env_destroy@Base 2.0.7
+       env_export@Base 2.0.7
+       env_import@Base 2.0.7
+       env_get@Base 2.0.7
+       env_put@Base 2.0.7
+
+## fdflags
+       fdflags@Base 2.0.7
+
+## fdpass
+       fdpass_recv@Base 2.3.3
+       fdpass_send@Base 2.3.3
+
+## fwatch
+       fwatch_init@Base 2.0.7
+       fwatch_initfd@Base 2.0.7
+       fwatch_update@Base 2.0.7
+       fwatch_updatefd@Base 2.0.7
+
+## lock
+       lock_file@Base 2.0.7
+
+## mdup
+       mdup@Base 2.1.0
+
+## tv
+       tv_add@Base 2.0.7
+       tv_addl@Base 2.0.7
+       tv_cmp@Base 2.0.7
+       tv_sub@Base 2.0.7
+       tv_subl@Base 2.0.7
+
+###--------------------------------------------------------------------------
+### Buffering.
+
+## lbuf
+       lbuf_init@Base 2.0.7
+       lbuf_destroy@Base 2.0.7
+       lbuf_snarf@Base 2.0.7
+       lbuf_free@Base 2.0.7
+       lbuf_flush@Base 2.0.7
+       lbuf_close@Base 2.0.7
+       lbuf_setsize@Base 2.0.7
+
+## pkbuf
+       pkbuf_init@Base 2.0.7
+       pkbuf_destroy@Base 2.0.7
+       pkbuf_snarf@Base 2.0.7
+       pkbuf_free@Base 2.0.7
+       pkbuf_flush@Base 2.0.7
+       pkbuf_want@Base 2.0.7
+       pkbuf_close@Base 2.0.7
+
+###--------------------------------------------------------------------------
+### Event-driven networking.
+
+## sel
+       sel_init@Base 2.0.7
+       sel_select@Base 2.0.7
+       sel_force@Base 2.0.7
+       sel_initfile@Base 2.0.7
+       sel_addfile@Base 2.0.7
+       sel_rmfile@Base 2.0.7
+       sel_addhook@Base 2.0.7
+       sel_rmhook@Base 2.0.7
+       sel_addtimer@Base 2.0.7
+       sel_rmtimer@Base 2.0.7
+       sel_fdmerge@Base 2.0.7
+
+## selbuf
+       selbuf_init@Base 2.0.7
+       selbuf_destroy@Base 2.0.7
+       selbuf_enable@Base 2.0.7
+       selbuf_disable@Base 2.0.7
+       selbuf_setsize@Base 2.0.7
+
+## selpk
+       selpk_init@Base 2.0.7
+       selpk_destroy@Base 2.0.7
+       selpk_want@Base 2.0.7
+       selpk_enable@Base 2.0.7
+       selpk_disable@Base 2.0.7
+
+## ident
+       ident@Base 2.2.5
+       ident_socket@Base 2.2.5
+       ident_abort@Base 2.0.7
+
+## conn
+       conn_init@Base 2.0.7
+       conn_fd@Base 2.0.7
+       conn_kill@Base 2.0.7
+
+## sig
+       sig_init@Base 2.0.7
+       sig_add@Base 2.0.7
+       sig_remove@Base 2.0.7
+
+## bres
+       bres_init@Base 2.0.7
+       bres_exec@Base 2.0.7
+       bres_byname@Base 2.0.7
+       bres_byaddr@Base 2.0.7
+       bres_abort@Base 2.0.7
+
+###--------------------------------------------------------------------------
+### Testing.
+
+## test
+       test_do@Base 2.0.7
+       test_run@Base 2.0.7
+       type_hex@Base 2.0.7
+       type_int@Base 2.0.7
+       type_long@Base 2.0.7
+       type_string@Base 2.0.7
+       type_uint32@Base 2.0.7
+       type_ulong@Base 2.0.7
+
+###--------------------------------------------------------------------------
+### Tracing.
+
+## trace
+       trace_on@Base 2.0.7
+       tracing@Base 2.0.7
+       trace_level@Base 2.0.7
+       traceopt@Base 2.0.7
+       trace@Base 2.0.7
+       trace_block@Base 2.0.7
+       trace_custom@Base 2.0.7
+
+###----- That's all, folks --------------------------------------------------
diff --git a/debian/compat b/debian/compat
new file mode 100644 (file)
index 0000000..f599e28
--- /dev/null
@@ -0,0 +1 @@
+10
diff --git a/debian/control b/debian/control
new file mode 100644 (file)
index 0000000..54a8976
--- /dev/null
@@ -0,0 +1,85 @@
+Source: mlib
+Section: libs
+Priority: extra
+Maintainer: Mark Wooding <mdw@distorted.org.uk>
+Build-Depends: debhelper (>= 10), python, libadns1-dev
+Standards-Version: 3.1.1
+
+Package: mlib2
+Architecture: any
+Multi-Arch: same
+Depends: ${shlibs:Depends}
+Pre-Depends: ${misc:Pre-Depends}
+Conflicts: mlib2-adns
+Description: A library of miscellaneous stuff
+ The mLib library provides various handy utilities, including
+   * yet another options parser, like GNU getopt but more so;
+   * a simple but efficient universal hashing family;
+   * a suite for writing event-driven select-based servers;
+   * a simple exception-handling system, based on longjmp;
+   * dynamically resizing strings and arrays;
+   * a resizing hashtable;
+   * base64 and hex encoding and decoding; and
+   * a simple background DNS resolver.
+ This package provides the mLib run-time library.  It uses an
+ implementation of the background resolver forks and calls
+ gethostbyname, so it therefore (a) depends only on the standard
+ C library, and (b) is distributed under the terms of the GNU
+ LGPL or GPL.
+
+Package: mlib2-adns
+Architecture: any
+Multi-Arch: same
+Depends: ${shlibs:Depends}
+Conflicts: mlib2
+Description: A library of miscellaneous stuff
+ The mLib library provides various handy utilities, including
+   * yet another options parser, like GNU getopt but more so;
+   * a simple but efficient universal hashing family;
+   * a suite for writing event-driven select-based servers;
+   * a simple exception-handling system, based on longjmp;
+   * dynamically resizing strings and arrays;
+   * a resizing hashtable;
+   * base64 and hex encoding and decoding; and
+   * a simple background DNS resolver.
+ This package provides the mLib run-time library.  It uses an
+ implementation of the background resolver based on the GNU adns
+ resolver, and therefore (a) depends on the libadns1 package, and
+ (b) is distributed under the terms of the full GPL only.
+
+Package: mlib-dev
+Architecture: any
+Depends: mlib2 (= ${binary:Version}) | mlib2-adns (= ${binary:Version}),
+ libc6-dev
+Recommends: mlib-bin
+Section: devel
+Description: A library of miscellaneous stuff
+ The mLib library provides various handy utilities, including
+   * yet another options parser, like GNU getopt but more so;
+   * a simple but efficient universal hashing family;
+   * a suite for writing event-driven select-based servers;
+   * a simple exception-handling system, based on longjmp;
+   * dynamically resizing strings and arrays;
+   * a resizing hashtable;
+   * base64 and hex encoding and decoding; and
+   * a simple background DNS resolver.
+ This package contains the header files and static libraries needed to
+ compile programs which use mLib.
+
+Package: mlib-bin
+Architecture: any
+Depends: ${shlibs:Depends}
+Recommends: mlib-dev
+Section: utils
+Description: A library of miscellaneous stuff
+ The mLib library provides various handy utilities, including
+   * yet another options parser, like GNU getopt but more so;
+   * a simple but efficient universal hashing family;
+   * a suite for writing event-driven select-based servers;
+   * a simple exception-handling system, based on longjmp;
+   * dynamically resizing strings and arrays;
+   * a resizing hashtable;
+   * base64 and hex encoding and decoding; and
+   * a simple background DNS resolver.
+ This package contains some utility programs: in particular, to generate
+ static tables for efficient CRC and universal hashing computations.
diff --git a/debian/copyright b/debian/copyright
new file mode 100644 (file)
index 0000000..c712ccc
--- /dev/null
@@ -0,0 +1,29 @@
+Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
+Copyright: 1997--2017 Straylight/Edgeware
+Upstream-Name: mLib
+Upstream-Contact: Mark Wooding <mdw@distorted.org.uk>
+Source: https://ftp.distorted.org.uk/pub/mdw/
+License: LGPL-2.0+
+
+Files: *
+Copyright: 1997--2017 Straylight/Edgeware
+License: LGPL-2.0+
+
+License: LGPL-2.0+
+ mLib is free software; you can redistribute it and/or modify it under
+ the terms of the GNU Library General Public License as published by the
+ Free Software Foundation; either version 2 of the License, or (at your
+ option) any later version.
+ .
+ mLib is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Library General Public
+ License for more details.
+ .
+ You should have received a copy of the GNU Library General Public
+ License along with mLib; if not, write to the Free Software Foundation,
+ Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ .
+ On Debian systems, the full text of the GNU Library General Public
+ License version 2 can be found in the file
+ `/usr/share/common-licenses/LGPL-2'.
diff --git a/debian/mlib-bin.install b/debian/mlib-bin.install
new file mode 100644 (file)
index 0000000..58cd505
--- /dev/null
@@ -0,0 +1,2 @@
+debian/tmp/usr/bin
+debian/tmp/usr/share/man/man1
diff --git a/debian/mlib-dev.install b/debian/mlib-dev.install
new file mode 100644 (file)
index 0000000..31f5cb5
--- /dev/null
@@ -0,0 +1,6 @@
+debian/tmp/usr/include
+debian/tmp/usr/lib/*/libmLib.a
+debian/tmp/usr/lib/*/libmLib.la
+debian/tmp/usr/lib/*/libmLib.so
+debian/tmp/usr/lib/*/pkgconfig
+debian/tmp/usr/share/man/man3
diff --git a/debian/mlib2-adns.copyright b/debian/mlib2-adns.copyright
new file mode 100644 (file)
index 0000000..5070c55
--- /dev/null
@@ -0,0 +1,26 @@
+mLib is copyright (c) 2003 Straylight/Edgeware
+
+mLib is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the
+Free Software Foundation; either version 2 of the License, or (at your
+option) any later version.
+
+mLib is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public
+License for more details.
+
+You should have a copy of the GNU General Public License in
+/usr/share/common-licenses/GPL-2; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
+USA.
+
+
+The usual version of mLib is distributed under the terms of the weaker
+GNU Library General Public License, version 2.  However, this version of
+mLib uses the GNU adns library as its background DNS resolver, and is
+therefore subject to the full GPL only.  This doesn't mean that you must
+apply the GPL to your program just because it uses mLib, even if it uses
+the `bres' background resolver interface: you only need to do that if
+you depend on the adns version specifically, e.g., to acheive some
+performance criterion.
diff --git a/debian/mlib2-adns.install.in b/debian/mlib2-adns.install.in
new file mode 100644 (file)
index 0000000..9879364
--- /dev/null
@@ -0,0 +1 @@
+debian/tmp-adns/usr/lib/@ARCH@/libmLib.so.* /usr/lib/@ARCH@
diff --git a/debian/mlib2-adns.symbols b/debian/mlib2-adns.symbols
new file mode 100644 (file)
index 0000000..2b9fdb9
--- /dev/null
@@ -0,0 +1 @@
+#include "common.symbols"
diff --git a/debian/mlib2.install b/debian/mlib2.install
new file mode 100644 (file)
index 0000000..ea99a01
--- /dev/null
@@ -0,0 +1,2 @@
+debian/tmp/usr/lib/*/libmLib.so.*
+debian/tmp/usr/lib/*/mLib/bres
diff --git a/debian/mlib2.symbols b/debian/mlib2.symbols
new file mode 100644 (file)
index 0000000..2b9fdb9
--- /dev/null
@@ -0,0 +1 @@
+#include "common.symbols"
diff --git a/debian/rules b/debian/rules
new file mode 100755 (executable)
index 0000000..4bd7c2c
--- /dev/null
@@ -0,0 +1,79 @@
+#! /usr/bin/make -f
+
+###--------------------------------------------------------------------------
+### Preliminary definitions.
+
+## The multiarch triple.
+DEB_HOST_MULTIARCH ?= $(shell dpkg-architecture -qDEB_HOST_MULTIARCH)
+a := $(DEB_HOST_MULTIARCH)
+
+## My version number, shorn of the Debian package version if any.
+DEB_UPSTREAM_VERSION ?= \
+       $(shell dpkg-parsechangelog | \
+               sed -n "/^Version: \([^-]*\)\(-.*\)\?/s//\1/p")
+v := $(DEB_UPSTREAM_VERSION)
+
+## Default Debhelper options.
+DH_OPTIONS = --parallel
+
+## Default Debhelper actions.
+%:; dh $@ $(DH_OPTIONS)
+
+###--------------------------------------------------------------------------
+### Multiple flavours.
+
+## The various flavours of the library which we must build.
+FLAVOURS = noadns adns
+
+## The build actions which we have to override.
+DH_BUILD_OVERRIDES = configure clean build install test
+
+## How to perform build action for a particular library flavour.
+define flavour-build-action
+dh_auto_$1 -Bdebian/build-$2 \
+       $(addprefix -O, $(DH_OPTIONS)) $(DH_OPTIONS_$2) \
+       $(DH_$1_OPTIONS) $(DH_$1_OPTIONS_$2)
+
+endef
+
+## Override the build actions, and perform the relevant action for each
+## flavour in turn.
+$(foreach t, $(DH_BUILD_OVERRIDES), dh-$t-hook):: %:; @:
+$(foreach t, $(DH_BUILD_OVERRIDES), override_dh_auto_$t): \
+               override_dh_auto_%: dh-%-hook
+       $(foreach f, $(FLAVOURS), $(call flavour-build-action,$*,$f))
+
+## Configuration options.
+DH_configure_OPTIONS = -- --libexecdir='$${libdir}'
+DH_configure_OPTIONS_noadns = --without-adns
+DH_configure_OPTIONS_adns = --with-adns
+
+## Cleaning options.
+dh-clean-hook::
+       rm -rf debian/tmp-adns
+
+## Installation options.
+DH_install_OPTIONS_adns = --destdir=debian/tmp-adns
+
+###--------------------------------------------------------------------------
+### Additional tweaks.
+
+## Don't try to rebuild our configure script.
+DH_OPTIONS += --without=autoreconf
+
+## Some of the install lists need to be generated.  This is a little
+## annoying.
+GEN_INSTALL_PKGS = mlib2-adns
+GEN_INSTALL_FILES = $(foreach p, $(GEN_INSTALL_PKGS), debian/$p.install)
+$(GEN_INSTALL_FILES): debian/%.install: \
+               debian/%.install.in debian/changelog debian/rules
+       sed 's,@ARCH@,$a,g' $< >$@.new && mv $@.new $@
+dh-install-hook:: $(GEN_INSTALL_FILES); @:
+dh-clean-hook::
+       rm -f $(GEN_INSTALL_FILES)
+
+## Check that the shared-library symbols are plausible.
+override_dh_makeshlibs:
+       dh_makeshlibs -- -c4
+
+###----- That's all, folks --------------------------------------------------
diff --git a/debian/source/format b/debian/source/format
new file mode 100644 (file)
index 0000000..d3827e7
--- /dev/null
@@ -0,0 +1 @@
+1.0
diff --git a/hash/Makefile.am b/hash/Makefile.am
new file mode 100644 (file)
index 0000000..877cd3a
--- /dev/null
@@ -0,0 +1,95 @@
+### -*-makefile-*-
+###
+### Build script for hashing
+###
+### (c) 2009 Straylight/Edgeware
+###
+
+###----- Licensing notice ---------------------------------------------------
+###
+### This file is part of the mLib utilities library.
+###
+### mLib is free software; you can redistribute it and/or modify
+### it under the terms of the GNU Library General Public License as
+### published by the Free Software Foundation; either version 2 of the
+### License, or (at your option) any later version.
+###
+### mLib is distributed in the hope that it will be useful,
+### but WITHOUT ANY WARRANTY; without even the implied warranty of
+### MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+### GNU Library General Public License for more details.
+###
+### You should have received a copy of the GNU Library General Public
+### License along with mLib; if not, write to the Free
+### Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+### MA 02111-1307, USA.
+
+include $(top_srcdir)/vars.am
+
+noinst_LTLIBRARIES      = libhash.la
+libhash_la_SOURCES      =
+nodist_libhash_la_SOURCES =
+libhash_la_LIBADD       =
+
+###--------------------------------------------------------------------------
+### Component files.
+
+## CRC32.
+pkginclude_HEADERS     += crc32.h
+libhash_la_SOURCES     += crc32.c
+LIBMANS                        += crc32.3
+
+bin_PROGRAMS           += crc-mktab
+crc_mktab_SOURCES       = crc-mktab.c
+crc_mktab_LDADD                 = $(UTIL_LIBS)
+PROGMANS               += crc-mktab.1
+
+nodist_libhash_la_SOURCES += ../precomp/crc32-tab.c
+PRECOMPS               += $(precomp)/crc32-tab.c
+if !CROSS_COMPILING
+$(precomp)/crc32-tab.c:
+       @$(mkdir_p) $(precomp)
+       @$(MAKE) crc-mktab$(EXEEXT)
+       $(AM_V_GEN)./crc-mktab -o $@.new \
+               -p0x04c11db7 -b32 -B8 -r -cC \
+               -scrc32_table -icrc32.h -tuint32 && \
+       mv $@.new $@
+endif
+
+check_PROGRAMS         += t/crc32.t
+t_crc32_t_SOURCES       = t/crc32-test.c
+t_crc32_t_CPPFLAGS      = $(TEST_CPPFLAGS)
+t_crc32_t_LDFLAGS       = -static
+
+EXTRA_DIST             += t/crc32.tests
+
+## Universal hashing.
+pkginclude_HEADERS     += unihash.h
+noinst_LTLIBRARIES     += libunihash.la
+libunihash_la_SOURCES   = unihash.c
+libhash_la_LIBADD      += libunihash.la
+LIBMANS                        += unihash.3
+
+bin_PROGRAMS           += unihash-mkstatic
+unihash_mkstatic_SOURCES = unihash-mkstatic.c
+unihash_mkstatic_LDADD  = libunihash.la $(UTIL_LIBS)
+PROGMANS               += unihash-mkstatic.1
+
+nodist_libhash_la_SOURCES += ../precomp/unihash-global.c
+PRECOMPS               += $(precomp)/unihash-global.c
+if !CROSS_COMPILING
+$(precomp)/unihash-global.c:
+       @$(mkdir_p) $(precomp)
+       @$(MAKE) unihash-mkstatic$(EXEEXT)
+       $(AM_V_GEN)./unihash-mkstatic -c -sunihash_global -iunihash.h \
+               -o$@.new && mv $@.new $@
+endif
+
+check_PROGRAMS         += t/unihash.t
+t_unihash_t_SOURCES     = t/unihash-test.c
+t_unihash_t_CPPFLAGS    = $(TEST_CPPFLAGS)
+t_unihash_t_LDFLAGS     = -static
+
+EXTRA_DIST             += t/unihash-testgen.py
+
+###----- That's all, folks --------------------------------------------------
diff --git a/hash/crc-mktab.1 b/hash/crc-mktab.1
new file mode 100644 (file)
index 0000000..bfbeb8a
--- /dev/null
@@ -0,0 +1,172 @@
+.\" nroff
+.ie t \{\
+.  ds ss \s8\u
+.  ds se \d\s0
+.\}
+.el \{\
+.  ds ss ^
+.  ds se
+.\}
+.TH crc-mktab 1 "9 November 2003" "Straylight/Edgeware" "mLib utilities library"
+.SH NAME
+crc-mktab \- construct CRC tables for efficient computation
+.SH SYNOPSIS
+.B crc-mktab
+.RB [ \-Ccr ]
+.RB [ \-s
+.IR symbol ]
+.RB [ \-t
+.IR type ]
+.RB [ \-i
+.IR header ]
+.br
+       \c
+.RB [ \-g
+.IR macro ]
+.RB [ \-b
+.IR bits ]
+.RB [ \-B
+.IR chunk ]
+.RB [ \-p
+.IR poly ]
+.br
+       \c
+.RB [ \-o
+.IR filename ]
+.SH DESCRIPTION
+The
+.B crc-mktab
+program constructs tables for efficient computation of CRC (cyclic
+redundancy check) values.  It will produce the table as either an array
+defined in a C source file or as an initializer macro defined in a C
+header file.
+.SS "Options"
+The program accepts no non-option arguments.  At least one of
+.B \-b
+or
+.B \-p
+must be given.  The options are as follows.
+.TP
+.B "\-h, \-\-help"
+Print a help message to standard output and exit successfully.
+.TP
+.B "\-v, \-\-version"
+Print the program's version number to standard output and exit
+successfully.
+.TP
+.B "\-u, \-\-usage"
+Print a one-line usage summary to standard output and exit successfully.
+.TP
+.B "\-C, \-\-const"
+When producing C source (the
+.B \-c
+option), rather than a header, define the table to be
+.BR const .
+.B "\-c, \-\-c-source"
+Produce a C source file which exports a symbol naming the array, instead
+of a C header file.
+.TP
+.BI "\-s, \-\-symbol=" symbol
+Name the table
+.IR symbol .
+This is the name of the macro defined by a header file, or the array
+exported by a C source.  The default macro name is
+.BR CRC_TAB ;
+the default array name is
+.BR crctab .
+.TP
+.BI "\-t, \-\-type=" type
+Specify the element type of the array defined by a C source output.  The
+default is
+.B "unsigned short"
+if the polynomial has degree 16 or less, or
+.B "unsigned long"
+otherwise.  This option does nothing without the
+.B \-c
+option.
+.TP
+.BI "\-i, \-\-include=" header
+Request that generated C source include the named
+.I header
+file.  Inserts a
+line of the form
+.PP
+.nf
+.BI "          #include """ header """"
+.fi
+.IP
+at the top of the generated C source.  The default is not to include a
+header file.  This option does nothing without the
+.B \-c
+option.
+.TP
+.BI "\-g, \-\-guard=" macro
+Use the named
+.I macro
+as a guard against multiple inclusion of the generated header file.
+Inserts a pair of lines of the form
+.PP
+.nf
+.BI "          #ifndef " macro
+.BI "          #define " macro
+.fi
+.IP
+at the top of the generated header, and a line
+.PP
+.nf
+.BI "          #endif"
+.fi
+.IP
+at the end.  The default guard macro name is built from the output file
+name specified with
+.B \-o
+by uppercasing all alphabetic characters in the name and replacing
+nonalphanumeric characters by underscores
+.RB ` _ '.
+This option does nothing with the
+.B \-c
+option.
+.TP
+.BI "\-b, \-\-bits=" bits
+Specifies the degree of the CRC polynomial or, equivalently, the length
+of the generated CRC.  This must be an integer between 1 and 32
+inclusive.  If it is not specified, the polynomial given by
+.B \-p
+is examined and an educated guess made.  (Currently we choose the
+smallest multiple of 8 which is large enough.)
+.TP
+.BI "\-B, \-\-bit-chunk=" chunk
+Chunk size in which bits are taken from the input.  The number of
+entries in the table is precisely
+.RI 2\*(ss chunk \*(se.
+The default chunk size is 8.
+.TP
+.BI "\-p, \-\-polynomial=" poly
+Specifies the CRC polynomial as an integer.  The polynomial has a
+nonzero coefficient in its
+.IR x \*(ss i \*(se
+term if and only if bit
+.I i
+of
+.I poly
+is nonzero.  Note that if you want to specify the polynomial in
+hexadecimal, you must prefix it with
+.BR 0x .
+Default polynomials of degree 16 and 32 are known.
+.TP
+.B "\-r, \-\-reverse"
+Construct the table to compensate for unusual bit-ordering.  Without
+this option, you'd have to reverse the order of all input chunks and the
+output CRC.
+.SS "The table and how to use it"
+Describing in detail the contents of the table would take too long.  For
+an example of use, see the header file
+.BR crc32.h .
+.SH "SEE ALSO"
+.BR crc32 (3).
+.PP
+.I A painless guide to CRC error detection algorithms
+by Ross N. Williams.
+.SH "AUTHOR"
+Mark Wooding, <mdw@distorted.org.uk>
+
diff --git a/hash/crc-mktab.c b/hash/crc-mktab.c
new file mode 100644 (file)
index 0000000..a176d16
--- /dev/null
@@ -0,0 +1,375 @@
+/* -*-c-*-
+ *
+ * Build CRC tables
+ *
+ * (c) 2000 Straylight/Edgeware
+ */
+
+/*----- Licensing notice --------------------------------------------------*
+ *
+ * This file is part of the mLib utilities library.
+ *
+ * mLib is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * mLib is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with mLib; if not, write to the Free
+ * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+ * MA 02111-1307, USA.
+ */
+
+/*----- Header files ------------------------------------------------------*/
+
+#include "config.h"
+
+#include <ctype.h>
+#include <errno.h>
+#include <limits.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "macros.h"
+#include "mdwopt.h"
+#include "quis.h"
+#include "report.h"
+
+/*----- Static variables --------------------------------------------------*/
+
+static unsigned long poly = 0;
+static const char *guard = 0;
+static unsigned bits = 0;
+static unsigned chunk = 8;
+static const char *file = 0;
+static unsigned flags = 0;
+static const char *sym = 0;
+static const char *type = 0;
+static const char *inc = 0;
+static FILE *fp;
+
+#define f_bogus 1u
+#define f_ctab 2u
+#define f_reverse 4u
+#define f_const 8u
+
+#define BSCOL 72
+
+/*----- Main code ---------------------------------------------------------*/
+
+static unsigned long getint(const char *p, unsigned long max,
+                           const char *what)
+{
+  char *pp;
+  unsigned long x = strtoul(p, &pp, 0);
+  if (*pp || (max && x > max))
+    die(EXIT_FAILURE, "bad %s `%s'", what, p);
+  return (x);
+}
+
+static void version(FILE *fp)
+{
+  pquis(fp, "$, mLib version " VERSION "\n");
+}
+
+static void usage(FILE *fp)
+{
+  pquis(fp, "Usage: $ [-Ccr] [-o FILE] [-g GUARD] [-s SYM] [-i HEADER]\n\
+       [-t TYPE] [-b BITS] [-B BITS] [-p POLY]\n");
+}
+
+static void help(FILE *fp)
+{
+  version(fp);
+  putc('\n', stdout);
+  usage(fp);
+  fputs("\n\
+Emits a table containing precomuted values for CRC algorithms.  A number\n\
+of options are provided:\n\
+\n\
+-h, --help             Show this help text.\n\
+-v, --version          Show the program's version number.\n\
+-u, --usage            Show a terse usage message.\n\
+\n\
+-c, --c-source         Emit a C source file rather than a header.\n\
+-C, --const            Declare table to be `const' (only useful with `-c').\n\
+-b, --bits=BITS                Emit a table for a BITS bits-wide CRC.\n\
+-B, --bit-chunk=BITS   Emit a table to process BITS bits at a time.\n\
+-p, --polynomial=POLY  Use the POLY as the dividing polynomial.\n\
+-r, --reverse          Create a `reversed' CRC table.\n\
+-g, --guard=GUARD      Use GUARD as a multiple-inclusion guard constant.\n\
+-i, --include=HEADER   Include HEADER at top of C source file.\n\
+-s, --symbol=SYM       Name the generated table SYM.\n\
+-t, --type=TYPE                Give the table entries type TYPE in C source.\n\
+-o, --output=FILE      Write the output to FILE.\n\
+", stdout);
+}
+
+unsigned long reflect(unsigned long x, unsigned b)
+{
+  unsigned long y = 0;
+  unsigned long xm, ym;
+  unsigned i;
+  if (!(flags & f_reverse))
+    return (x);
+  xm = 1;
+  ym = 1u << (b - 1);
+  for (i = 0; i < b; i++) {
+    if (x & xm)
+      y |= ym;
+    xm <<= 1;
+    ym >>= 1;
+  }
+  return (y);
+}
+
+int main(int argc, char *argv[])
+{
+  unsigned n, t, nw;
+  unsigned max, i;
+  unsigned long mask;
+
+  ego(argv[0]);
+
+  for (;;) {
+    static struct option opts[] = {
+      { "help",                0,              0,      'h' },
+      { "version",     0,              0,      'v' },
+      { "usage",       0,              0,      'u' },
+
+      { "output",      OPTF_ARGREQ,    0,      'o' },
+      { "c-source",    0,              0,      'c' },
+      { "const",       0,              0,      'C' },
+      { "symbol",      OPTF_ARGREQ,    0,      's' },
+      { "type",                OPTF_ARGREQ,    0,      't' },
+      { "include",     OPTF_ARGREQ,    0,      'i' },
+      { "guard",       OPTF_ARGREQ,    0,      'g' },
+
+      { "bits",                OPTF_ARGREQ,    0,      'b' },
+      { "bit-chunk",   OPTF_ARGREQ,    0,      'B' },
+      { "polynomial",  OPTF_ARGREQ,    0,      'p' },
+      { "reverse",     0,              0,      'r' },
+
+      { 0,             0,              0,      0 }
+    };
+    int i = mdwopt(argc, argv, "hvu o:cCs:t:i:g: b:B:p:r", opts, 0, 0, 0);
+
+    if (i < 0)
+      break;
+    switch (i) {
+      case 'h':
+       help(stdout);
+       exit(0);
+      case 'v':
+       version(stdout);
+       exit(0);
+      case 'u':
+       usage(stdout);
+       exit(0);
+
+      case 'o':
+       file = optarg;
+       break;
+      case 'c':
+       flags |= f_ctab;
+       break;
+      case 'C':
+       flags |= f_const;
+       break;
+      case 's':
+       sym = optarg;
+       break;
+      case 't':
+       type = optarg;
+       break;
+      case 'i':
+       inc = optarg;
+       break;
+      case 'g':
+       guard = optarg;
+       break;
+
+      case 'b':
+       bits = getint(optarg, 32, "CRC width");
+       break;
+      case 'B':
+       chunk = getint(optarg, 32, "chunk size");
+       break;
+      case 'p':
+       poly = getint(optarg, 0xffffffff, "CRC poly");
+       break;
+      case 'r':
+       flags |= f_reverse;
+       break;
+
+      default:
+       flags |= f_bogus;
+       break;
+    }
+  }
+  if ((flags & f_bogus) || optind != argc) {
+    usage(stderr);
+    exit(EXIT_FAILURE);
+  }
+
+  /* --- Sort out the various parameters --- */
+
+  if (!poly) {
+    switch (bits) {
+      case 16:
+       if (flags & f_reverse)
+         poly = 0x8408;
+       else
+         poly = 0x1021;
+       break;
+      case 32:
+       poly = 0x04c11db7;
+       flags |= f_reverse;
+       bits = 32;
+       break;
+      case 0:
+       die(EXIT_FAILURE, "no polynomial or bit width set");
+       break;
+      default:
+       die(EXIT_FAILURE, "no standard polynomials for %u bits", bits);
+       break;
+    }
+  }
+
+  if (!bits) {
+    unsigned long x = poly;
+    while (x > 1) {
+      x >>= 8;
+      bits += 8;
+    }
+  }
+  nw = (bits + 3)/4;
+
+  if ((flags & f_ctab) && !type)
+    type = bits > 16 ? "unsigned long" : "unsigned short";
+
+  /* --- Start output --- */
+
+  if (!file)
+    fp = stdout;
+  else {
+    if (!(flags & f_ctab) && !guard) {
+      char *p;
+      const char *q;
+      if ((p = malloc(strlen(file) + 1)) == 0)
+       die(EXIT_FAILURE, "not enough memory");
+      guard = p;
+      for (q = file; *q; p++, q++) {
+       if (ISALNUM(*q))
+         *p = TOUPPER(*q);
+       else
+         *p = '_';
+      }
+      *p++ = 0;
+    }
+    if ((fp = fopen(file, "w")) == 0)
+      die(EXIT_FAILURE, "couldn't write `%s': %s", file, strerror(errno));
+  }
+
+  if (!sym)
+    sym = (flags & f_ctab) ? "crctab" : "CRC_TAB";
+
+  /* --- Dump out the first chunk of the file --- */
+
+  fprintf(fp, "\
+/* -*-c-*-\n\
+ *\n\
+ * CRC table (poly = %0*lx%s) [generated]\n\
+ */\n\
+\n",
+         nw, poly, flags & f_reverse ? "; reversed" : "");
+
+  if (flags & f_ctab) {
+    if (inc)
+      fprintf(fp, "#include \"%s\"\n\n", inc);
+    fprintf(fp, "%s%s %s[] = {\n",
+           (flags&f_const) ? "const " : "", type, sym);
+  } else {
+    int n;
+    if (guard)
+      fprintf(fp, "#ifndef %s\n#define %s\n\n", guard, guard);
+    n = fprintf(fp, "#define %s {", sym);
+    while (n < BSCOL) {
+      fputc('\t', fp);
+      n = (n + 8) & ~7;
+    }
+    fputc('\n', fp);
+  }
+
+  /* --- Sort out the line width --- */
+
+  n = 1;
+  while (2 + n * (nw + 4) < BSCOL)
+    n <<= 1;
+  n >>= 1;
+  max = 1 << chunk;
+  if (n > max)
+    n = max;
+  t = 0;
+  while (((1 + n * (nw + 4)) & ~7) + 8 * t < BSCOL)
+    t++;
+
+  /* --- Start grinding --- */
+
+  mask = 0xffffffff >> (32 - bits);
+  fputc(' ', fp);
+  for (i = 0; i < max; i++) {
+    unsigned long x, y, z;
+    unsigned j;
+    unsigned ni, nn;
+
+    x = reflect(i, chunk);
+    y = 0;
+    nn = chunk;
+    while (nn) {
+      ni = bits;
+      if (ni > nn)
+       ni = nn;
+      z = (x >> (nn - ni)) & (0xffffffff >> (32 - ni));
+      y ^= z << (bits - ni);
+      for (j = 0; j < ni; j++)
+       y = ((y << 1) ^ (y & (1 << (bits - 1)) ? poly : 0)) & mask;
+      nn -= ni;
+    }
+    x = reflect(y, bits);
+
+    fprintf(fp, " 0x%0*lx", nw, x);
+    if (i == max - 1) {
+      if (!(flags & f_ctab) && ((2 + n * (nw + 4)) & ~7) + 8 * t < BSCOL)
+       fputc('\t', fp);
+    } else
+      fputc(',', fp);
+    if (i + 1 == max || (i + 1)%n == 0) {
+      if (!(flags & f_ctab)) {
+       for (j = 0; j < t; j++)
+         fputc('\t', fp);
+       fputc('\\', fp);
+      }
+      fputc('\n', fp);
+      if (i + 1 != max)
+       fputc(' ', fp);
+    }
+  }
+
+  /* --- Done --- */
+
+  fputs(flags & f_ctab ? "};\n" : "}\n", fp);
+  if (!(flags & f_ctab) && guard)
+    fputs("\n#endif\n", fp);
+
+  return (0);
+}
+
+/*----- That's all, folks -------------------------------------------------*/
diff --git a/hash/crc32.3 b/hash/crc32.3
new file mode 100644 (file)
index 0000000..6eca843
--- /dev/null
@@ -0,0 +1,59 @@
+.\" -*-nroff-*-
+.ie t \{\
+.  ds ss \s8\u
+.  ds se \d\s0
+.\}
+.el \{\
+.  ds ss ^
+.  ds se
+.\}
+.TH crc32 3 "8 May 1999" "Straylight/Edgeware" "mLib utilities library"
+.SH NAME
+crc32 \- calculate 32-bit CRC
+.\" @crc32
+.SH SYNOPSIS
+.nf
+.B "#include <mLib/crc32.h>"
+
+.BI "uint32 crc32(uint32 " crc ", const void *" buf ", size_t " sz );
+.BI CRC32( result ", " crc ", " buf ", " sz )
+.fi
+.SH DESCRIPTION
+The
+.B crc32
+function calculates a 32-bit cyclic redundancy check of the data block
+at address
+.I buf
+and size
+.I sz
+passed to it.
+.PP
+The function is restartable.  For a first call, pass zero as the value
+of the
+.I crc
+argument; for subsequent blocks, pass in the previous output.  The final
+answer is equal to the result you'd get from computing a CRC over the
+concatenation of the individual blocks.
+.PP
+The
+.B CRC32
+macro calculates a CRC inline.  The calculated CRC value is placed in
+the variable named by
+.IR result .
+Only use the macro version when efficiency is a major concern: it makes
+the code rather harder to read.
+.PP
+Note that a CRC is not cryptographically strong: it's fairly easy for an
+adversary to construct blocks of data with any desired CRC, or to modify
+a given block in a way which doesn't change its (unknown) CRC.
+.PP
+The exact behaviour of the CRC is beyond the scope of this manual;
+suffice to say that the result is, in some suitable representation, the
+remainder after division by a degree-32 polynomial in GF(2)[x].
+.SH "RETURN VALUE"
+The return value is the 32-bit CRC of the input block.
+.SH "SEE ALSO"
+.BR unihash (3),
+.BR mLib (3).
+.SH AUTHOR
+Mark Wooding, <mdw@distorted.org.uk>
diff --git a/hash/crc32.c b/hash/crc32.c
new file mode 100644 (file)
index 0000000..f27ceeb
--- /dev/null
@@ -0,0 +1,86 @@
+/* -*-c-*-
+ *
+ * Calculating cyclic redundancy values (non-cryptographic!)
+ *
+ * (c) 1998 Straylight/Edgeware
+ */
+
+/*----- Licensing notice --------------------------------------------------*
+ *
+ * This file is part of the mLib utilities library.
+ *
+ * mLib is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * mLib is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with mLib; if not, write to the Free
+ * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+ * MA 02111-1307, USA.
+ */
+
+/*----- Header files ------------------------------------------------------*/
+
+/* --- ANSI headers --- */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+/* --- Local headers --- */
+
+#include "bits.h"
+#include "crc32.h"
+
+/*----- Functionc provided ------------------------------------------------*/
+
+/* --- @crc32@ --- *
+ *
+ * Arguments:  @uint32 crc@ = carryover from previous call, or zero
+ *             @const void *buf@ = pointer to buffer to check
+ *             @size_t sz@ = size of the buffer
+ *
+ * Returns:    The CRC updated by the new buffer.
+ *
+ * Use:                A restartable CRC calculator.  This is just a function
+ *             wrapper for the macro version.
+ */
+
+uint32 crc32(uint32 crc, const void *buf, size_t sz)
+{
+  uint32 c;
+  CRC32(c, crc, buf, sz);
+  return (c);
+}
+
+/*----- Test driver -------------------------------------------------------*/
+
+#ifdef TEST_RIG
+
+#include <stdio.h>
+
+int main(void)
+{
+  uint32 crc = 0;
+  char buf[BUFSIZ];
+  int r;
+
+  do {
+    r = fread(buf, 1, sizeof(buf), stdin);
+    if (r > 0)
+      crc = crc32(crc, buf, r);
+  } while (r == sizeof(buf));
+
+  printf("crc32(stdin) = %08lx\n", (unsigned long)crc);
+  return (0);
+}
+
+#endif
+
+/*----- That's all, folks -------------------------------------------------*/
diff --git a/hash/crc32.h b/hash/crc32.h
new file mode 100644 (file)
index 0000000..429306e
--- /dev/null
@@ -0,0 +1,89 @@
+/* -*-c-*-
+ *
+ * Calculating cyclic redundancy values (non-cryptographic!)
+ *
+ * (c) 1998 Straylight/Edgeware
+ */
+
+/*----- Licensing notice --------------------------------------------------*
+ *
+ * This file is part of the mLib utilities library.
+ *
+ * mLib is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * mLib is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with mLib; if not, write to the Free
+ * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+ * MA 02111-1307, USA.
+ */
+
+#ifndef MLIB_CRC32_H
+#define MLIB_CRC32_H
+
+#ifdef __cplusplus
+  extern "C" {
+#endif
+
+/*----- Header files ------------------------------------------------------*/
+
+#ifndef MLIB_BITS_H
+#  include "bits.h"
+#endif
+
+/*----- External values ---------------------------------------------------*/
+
+extern const uint32 crc32_table[256];
+
+/*----- Macros ------------------------------------------------------------*/
+
+/* --- @CRC32@ --- *
+ *
+ * Arguments:  @uint32 result@ = where to put the result
+ *             @uint32 crc@ = carryover from previous call, or zero
+ *             @void *buf@ = pointer to buffer to check
+ *             @size_t sz@ = size of the buffer
+ *
+ * Use:                A restartable CRC calculator wrapped up in a macro.
+ */
+
+#define CRC32(result, crc, buf, sz) do {                               \
+  const octet *_p = (const octet *)(buf);                              \
+  const octet *_l = _p + (sz);                                         \
+  uint32 _crc = U32(~(crc));                                           \
+                                                                       \
+  while (_p < _l)                                                      \
+    _crc = (_crc >> 8) ^ crc32_table[U8(*_p++ ^ _crc)];                        \
+  (result) = U32(~_crc);                                               \
+} while (0)
+
+/*----- Functions provided ------------------------------------------------*/
+
+/* --- @crc32@ --- *
+ *
+ * Arguments:  @uint32 crc@ = carryover from previous call, or zero
+ *             @const void *buf@ = pointer to buffer to check
+ *             @size_t sz@ = size of the buffer
+ *
+ * Returns:    The CRC updated by the new buffer.
+ *
+ * Use:                A restartable CRC calculator.  This is just a function
+ *             wrapper for the macro version.
+ */
+
+extern uint32 crc32(uint32 /*crc*/, const void */*buf*/, size_t /*sz*/);
+
+/*----- That's all, folks -------------------------------------------------*/
+
+#ifdef __cplusplus
+  }
+#endif
+
+#endif
diff --git a/hash/t/crc32-test.c b/hash/t/crc32-test.c
new file mode 100644 (file)
index 0000000..8e6d820
--- /dev/null
@@ -0,0 +1,91 @@
+/* -*-c-*-
+ *
+ * Test driver for CRC
+ *
+ * (c) 2009 Straylight/Edgeware
+ */
+
+/*----- Licensing notice --------------------------------------------------*
+ *
+ * This file is part of the mLib utilities library.
+ *
+ * mLib is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * mLib is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with mLib; if not, write to the Free
+ * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+ * MA 02111-1307, USA.
+ */
+
+/*----- Header files ------------------------------------------------------*/
+
+#include <stdio.h>
+
+#include "crc32.h"
+#include "testrig.h"
+
+/*----- Main code ---------------------------------------------------------*/
+
+static int verify(dstr *v)
+{
+  uint32 h, hh;
+  size_t n;
+  int i, c;
+  const char *p;
+  int ok = 1;
+
+  static const int step[] = { 0, 1, 5, 6, 7, 8, 23, -1 };
+
+  /* --- Set up for using this key --- */
+
+  h = *(uint32 *)v[1].buf;
+
+  /* --- Hash the data a lot --- */
+
+  for (i = 0; step[i] >= 0; i++) {
+    c = step[i];
+    if (!c)
+      hh = crc32(0, v[0].buf, v[0].len);
+    else {
+      hh = 0;
+      p = v[0].buf;
+      n = v[0].len;
+      while (n) {
+       if (c > n) c = n;
+       hh = crc32(hh, p, c);
+       p += c;
+       n -= c;
+      }
+    }
+    if (h != hh) {
+      ok = 0;
+      fprintf(stderr, "\ncrc32 failed\n");
+      fprintf(stderr, "         data = %s\n", v[0].buf);
+      fprintf(stderr, "         step = %d\n", step[i]);
+      fprintf(stderr, "         expected = %08lx\n", (unsigned long)h);
+      fprintf(stderr, "         computed = %08lx\n", (unsigned long)hh);
+    }
+  }
+  return (ok);
+}
+
+static const test_chunk tests[] = {
+  { "hash", verify, { &type_string, &type_uint32 } },
+  { 0, 0, { 0 } }
+};
+
+int main(int argc, char *argv[])
+{
+  test_run(argc, argv, tests, "crc32.in");
+  return (0);
+}
+
+/*----- That's all, folks -------------------------------------------------*/
diff --git a/hash/t/crc32.tests b/hash/t/crc32.tests
new file mode 100644 (file)
index 0000000..9fb2baf
--- /dev/null
@@ -0,0 +1,11 @@
+### -*-conf-*-
+### crc32 tests
+
+hash {
+  ""                                                           0;
+  "foo"                                                                0x8c736521;
+  "anything you like"                                          0x2c090211;
+  "an exaple test string"                                      0x686a05aa;
+  "The quick brown fox jumps over the lazy dog."               0x519025e9;
+  "A man, a plan, a canal: Panama!"                            0x27f3faee;
+}
diff --git a/hash/t/unihash-test.c b/hash/t/unihash-test.c
new file mode 100644 (file)
index 0000000..3e56f10
--- /dev/null
@@ -0,0 +1,96 @@
+/* -*-c-*-
+ *
+ * Test driver for universal hashing
+ *
+ * (c) 2009 Straylight/Edgeware
+ */
+
+/*----- Licensing notice --------------------------------------------------*
+ *
+ * This file is part of the mLib utilities library.
+ *
+ * mLib is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * mLib is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with mLib; if not, write to the Free
+ * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+ * MA 02111-1307, USA.
+ */
+
+/*----- Header files ------------------------------------------------------*/
+
+#include <stdio.h>
+
+#include "unihash.h"
+#include "testrig.h"
+
+/*----- Main code ---------------------------------------------------------*/
+
+static int verify(dstr *v)
+{
+  unihash_info ui;
+  uint32 k;
+  uint32 h, hh;
+  size_t n;
+  int i, c;
+  const char *p;
+  int ok = 1;
+
+  static const int step[] = { 0, 1, 5, 6, 7, 8, 23, -1 };
+
+  /* --- Set up for using this key --- */
+
+  k = *(uint32 *)v[0].buf;
+  h = *(uint32 *)v[2].buf;
+  unihash_setkey(&ui, k);
+
+  /* --- Hash the data a lot --- */
+
+  for (i = 0; step[i] >= 0; i++) {
+    c = step[i];
+    if (!c)
+      hh = unihash(&ui, v[1].buf, v[1].len);
+    else {
+      hh = UNIHASH_INIT(&ui);
+      p = v[1].buf;
+      n = v[1].len;
+      while (n) {
+       if (c > n) c = n;
+       hh = unihash_hash(&ui, hh, p, c);
+       p += c;
+       n -= c;
+      }
+    }
+    if (h != hh) {
+      ok = 0;
+      fprintf(stderr, "\nunihash failed\n");
+      fprintf(stderr, "         key = %08lx\n", (unsigned long)k);
+      fprintf(stderr, "         data = %s\n", v[1].buf);
+      fprintf(stderr, "         step = %d\n", step[i]);
+      fprintf(stderr, "         expected = %08lx\n", (unsigned long)h);
+      fprintf(stderr, "         computed = %08lx\n", (unsigned long)hh);
+    }
+  }
+  return (ok);
+}
+
+static const test_chunk tests[] = {
+  { "hash", verify, { &type_uint32, &type_string, &type_uint32 } },
+  { 0, 0, { 0 } }
+};
+
+int main(int argc, char *argv[])
+{
+  test_run(argc, argv, tests, "unihash.in");
+  return (0);
+}
+
+/*----- That's all, folks -------------------------------------------------*/
diff --git a/hash/t/unihash-testgen.py b/hash/t/unihash-testgen.py
new file mode 100644 (file)
index 0000000..1fde21f
--- /dev/null
@@ -0,0 +1,42 @@
+#! /usr/bin/python
+### -*-python-*-
+###
+### Generate test vectors for universal hashing.
+
+import sys as SYS
+if SYS.version_info >= (3,): xrange = range
+
+MOD = 0x104c11db7
+
+def gfmul(x, y):
+  a = 0
+  while y > 0:
+    if y & 1: a ^= x
+    if x & 0x80000000: x = (x << 1) ^ MOD
+    else: x <<= 1
+    y >>= 1
+  return a
+
+def hashtest(k, m):
+  h = k
+  for ch in m: h = gfmul(h ^ ord(ch), k)
+  print('  0x%08x "%s" 0x%08x;' % (k, m, h))
+
+print('''\
+### Test vectors for universal hashing
+###   [generated]
+
+hash {''')
+
+for k, m in [(0x00000000, 'anything you like'),
+             (0x12345678, 'an exaple test string'),
+             (0xb8a171f0, 'The quick brown fox jumps over the lazy dog.'),
+             (0x2940521b, 'A man, a plan, a canal: Panama!')]:
+  hashtest(k, m)
+
+k, m = 0x94b22a73, 0xbb7b1fef
+for i in xrange(48):
+  hashtest(k, "If we don't succeed, we run the risk of failure.")
+  k = gfmul(k, m)
+
+print('}')
diff --git a/hash/tests.at b/hash/tests.at
new file mode 100644 (file)
index 0000000..65456d8
--- /dev/null
@@ -0,0 +1,49 @@
+### -*-autotest-*-
+###
+### Test script for hashing
+###
+### (c) 2009 Straylight/Edgeware
+###
+
+###----- Licensing notice ---------------------------------------------------
+###
+### This file is part of the mLib utilities library.
+###
+### mLib is free software; you can redistribute it and/or modify
+### it under the terms of the GNU Library General Public License as
+### published by the Free Software Foundation; either version 2 of the
+### License, or (at your option) any later version.
+###
+### mLib is distributed in the hope that it will be useful,
+### but WITHOUT ANY WARRANTY; without even the implied warranty of
+### MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+### GNU Library General Public License for more details.
+###
+### You should have received a copy of the GNU Library General Public
+### License along with mLib; if not, write to the Free
+### Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+### MA 02111-1307, USA.
+
+###--------------------------------------------------------------------------
+### Tests.
+
+## crc32
+AT_SETUP([hash: crc32])
+AT_KEYWORDS([hash crc32])
+
+AT_CHECK([BUILDDIR/t/crc32.t -f SRCDIR/t/crc32.tests],
+        [0], [ignore], [ignore])
+
+AT_CLEANUP
+
+## unihash
+AT_SETUP([hash: unihash])
+AT_KEYWORDS([hash unihash])
+
+$PYTHON SRCDIR/t/unihash-testgen.py >unihash.tests
+AT_CHECK([BUILDDIR/t/unihash.t -f unihash.tests],
+        [0], [ignore], [ignore])
+
+AT_CLEANUP
+
+###----- That's all, folks --------------------------------------------------
diff --git a/hash/unihash-mkstatic.1 b/hash/unihash-mkstatic.1
new file mode 100644 (file)
index 0000000..09da499
--- /dev/null
@@ -0,0 +1,126 @@
+.\" nroff
+.ie t \{\
+.  ds ss \s8\u
+.  ds se \d\s0
+.\}
+.el \{\
+.  ds ss ^
+.  ds se
+.\}
+.TH unihash-mkstatic 1 "14 December 2003" "Straylight/Edgeware" "mLib utilities library"
+.SH NAME
+unihash-mkstatic \- construct tables for universal hashing
+.SH SYNOPSIS
+.B unihash-mkstatic
+.RB [ \-Cc ]
+.RB [ \-s
+.IR symbol ]
+.RB [ \-i
+.IR header ]
+.RB [ \-g
+.IR macro ]
+.br
+       \c
+.RB [ \-k
+.IR key ]
+.RB [ \-o
+.IR filename ]
+.SH DESCRIPTION
+The
+.B unihash-mkstatic
+program constructs tables for efficient universal hashing (see
+.BR unihash (3)).
+It will produce the table as either an array defined in a C source file
+or as an initializer macro defined in a C header file.
+.SS "Options"
+The program accepts no non-option arguments.  The options are as
+follows.
+.TP
+.B "\-h, \-\-help"
+Print a help message to standard output and exit successfully.
+.TP
+.B "\-v, \-\-version"
+Print the program's version number to standard output and exit
+successfully.
+.TP
+.B "\-u, \-\-usage"
+Print a one-line usage summary to standard output and exit successfully.
+.TP
+.B "\-C, \-\-const"
+When producing C source (the
+.B \-c
+option), rather than a header, define the table to be
+.BR const .
+.TP
+.B "\-c, \-\-c-source"
+Produce a C source file which exports a symbol naming the array, instead
+of a C header file.
+.TP
+.BI "\-s, \-\-symbol=" symbol
+Name the table
+.IR symbol .
+This is the name of the macro defined by a header file, or the array
+exported by a C source.  The default macro name is
+.BR UHI_INIT ;
+the default array name is
+.BR uhi .
+.TP
+.BI "\-i, \-\-include=" header
+Request that generated C source include the named
+.I header
+file.  Inserts a
+line of the form
+.PP
+.nf
+.BI "          #include """ header """"
+.fi
+.IP
+at the top of the generated C source.  The default is not to include
+.BR <mLib/unihash.h> ,
+which is necessary to declare the
+.B unihash_info
+type.  This option does nothing without the
+.B \-c
+option.
+.TP
+.BI "\-g, \-\-guard=" macro
+Use the named
+.I macro
+as a guard against multiple inclusion of the generated header file.
+Inserts a pair of lines of the form
+.PP
+.nf
+.BI "          #ifndef " macro
+.BI "          #define " macro
+.fi
+.IP
+at the top of the generated header, and a line
+.PP
+.nf
+.BI "          #endif"
+.fi
+.IP
+at the end.  The default guard macro name is built from the output file
+name specified with
+.B \-o
+by uppercasing all alphabetic characters in the name and replacing
+nonalphanumeric characters by underscores
+.RB ` _ '.
+This option does nothing with the
+.B \-c
+option.
+.TP
+.BI "\-k, \-\-key=" key
+Specifies the hashing key as an integer.  Note that if you want to
+specify the key in hexadecimal, you must prefix it with
+.BR 0x .
+The default key is
+.BR 0xe07e5bd1 ,
+which is, as far as the author knows, as good as any other fixed value.
+.TP
+.SH "SEE ALSO"
+.BR crc-mktab (1),
+.BR unihash (3).
+.SH "AUTHOR"
+Mark Wooding, <mdw@distorted.org.uk>
+
diff --git a/hash/unihash-mkstatic.c b/hash/unihash-mkstatic.c
new file mode 100644 (file)
index 0000000..958b3d7
--- /dev/null
@@ -0,0 +1,265 @@
+/* -*-c-*-
+ *
+ * Build static universal hash tables
+ *
+ * (c) 2003 Straylight/Edgeware
+ */
+
+/*----- Licensing notice --------------------------------------------------*
+ *
+ * This file is part of the mLib utilities library.
+ *
+ * mLib is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * mLib is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with mLib; if not, write to the Free
+ * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+ * MA 02111-1307, USA.
+ */
+
+/*----- Header files ------------------------------------------------------*/
+
+#include "config.h"
+
+#include <ctype.h>
+#include <errno.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+
+#include "macros.h"
+#include "mdwopt.h"
+#include "quis.h"
+#include "report.h"
+#include "unihash.h"
+
+/*----- Main code ---------------------------------------------------------*/
+
+#define BSCOL 72
+
+static unsigned long getint(const char *p, unsigned long max,
+                           const char *what)
+{
+  char *pp;
+  unsigned long x = strtoul(p, &pp, 0);
+  if (*pp || (max && x > max))
+    die(EXIT_FAILURE, "bad %s `%s'", what, p);
+  return (x);
+}
+
+static void version(FILE *fp)
+{
+  pquis(fp, "$, mLib version " VERSION "\n");
+}
+
+static void usage(FILE *fp)
+{
+  pquis(fp, "Usage: $ [-Cc] [-o FILE] [-g GUARD] [-i HEADER] [-s SYM]\n\
+       [-k KEY]\n");
+}
+
+static void help(FILE *fp)
+{
+  version(fp);
+  putc('\n', stdout);
+  usage(fp);
+  fputs("\n\
+Emits a precomputed unihash_info structure for a given key.\n\
+\n\
+-h, --help             Show this help text.\n\
+-v, --version          Show the program's version number.\n\
+-u, --usage            Show a terse usage message.\n\
+\n\
+-c, --c-source         Emit a C source file rather than a header.\n\
+-C, --const            Declare table to be `const' (only useful with `-c').\n\
+-k, --key=KEY          Use KEY as the universal hashing key.\n\
+-g, --guard=GUARD      Use GUARD as a multiple-inclusion guard constant.\n\
+-i, --include=HEADER   Include HEADER at top of C source file.\n\
+-s, --symbol=SYM       Name the generated table SYM.\n\
+-o, --output=FILE      Write the output to FILE.\n\
+", stdout);
+}
+
+int main(int argc, char *argv[])
+{
+  uint32 key = 0xe07e5bd1;
+  unsigned flags = 0;
+  const char *sym = 0;
+  const char *inc = 0;
+  const char *guard = 0;
+  const char *file = 0;
+  FILE *fp;
+  unihash_info u;
+  int i, j, k;
+
+#define f_bogus 1u
+#define f_ctab 2u
+#define f_const 4u
+
+  ego(argv[0]);
+
+  for (;;) {
+    static struct option opts[] = {
+      { "help",                0,              0,      'h' },
+      { "version",     0,              0,      'v' },
+      { "usage",       0,              0,      'u' },
+
+      { "output",      OPTF_ARGREQ,    0,      'o' },
+      { "c-source",    0,              0,      'c' },
+      { "const",       0,              0,      'C' },
+      { "key",         OPTF_ARGREQ,    0,      'k' },
+      { "symbol",      OPTF_ARGREQ,    0,      's' },
+      { "include",     OPTF_ARGREQ,    0,      'i' },
+      { "guard",       OPTF_ARGREQ,    0,      'g' },
+
+      { 0,             0,              0,      0 }
+    };
+    int i = mdwopt(argc, argv, "hvu o:cCk:s:i:g:", opts, 0, 0, 0);
+
+    if (i < 0)
+      break;
+    switch (i) {
+      case 'h':
+       help(stdout);
+       exit(0);
+      case 'v':
+       version(stdout);
+       exit(0);
+      case 'u':
+       usage(stdout);
+       exit(0);
+
+      case 'o':
+       file = optarg;
+       break;
+      case 'c':
+       flags |= f_ctab;
+       break;
+      case 'C':
+       flags |= f_const;
+       break;
+      case 's':
+       sym = optarg;
+       break;
+      case 'i':
+       inc = optarg;
+       break;
+      case 'g':
+       guard = optarg;
+       break;
+      case 'k':
+       key = getint(optarg, 0xffffffff, "key");
+       break;
+
+      default:
+       flags |= f_bogus;
+       break;
+    }
+  }
+  if ((flags & f_bogus) || optind != argc) {
+    usage(stderr);
+    exit(EXIT_FAILURE);
+  }
+
+  /* --- Sort stuff out --- */
+
+  unihash_setkey(&u, key);
+  if (!sym)
+    sym = (flags & f_ctab) ? "uhi" : "UHI_INIT";
+
+  /* --- Start output --- */
+
+  if (!file)
+    fp = stdout;
+  else {
+    if (!(flags & f_ctab) && !guard) {
+      char *p;
+      const char *q;
+      if ((p = malloc(strlen(file) + 1)) == 0)
+       die(EXIT_FAILURE, "not enough memory");
+      guard = p;
+      for (q = file; *q; p++, q++) {
+       if (ISALNUM(*q))
+         *p = TOUPPER(*q);
+       else
+         *p = '_';
+      }
+      *p++ = 0;
+    }
+    if ((fp = fopen(file, "w")) == 0)
+      die(EXIT_FAILURE, "couldn't write `%s': %s", file, strerror(errno));
+  }
+
+  /* --- Dump out the first chunk of the file --- */
+
+  fprintf(fp, "\
+/* -*-c-*-\n\
+ *\n\
+ * Unihash table (key = %08lx) [generated]\n\
+ */\n\
+\n",
+         (unsigned long)key);
+
+  if (flags & f_ctab) {
+    if (inc)
+      fprintf(fp, "#include \"%s\"\n\n", inc);
+    else
+      fputs("#include <mLib/unihash.h>\n\n", fp);
+    fprintf(fp, "%sunihash_info %s = { {\n",
+           (flags&f_const) ? "const " : "", sym);
+  } else {
+    int n;
+    if (guard)
+      fprintf(fp, "#ifndef %s\n#define %s\n\n", guard, guard);
+    n = fprintf(fp, "#define %s { {", sym);
+    while (n < BSCOL) {
+      fputc('\t', fp);
+      n = (n + 8) & ~7;
+    }
+    fputc('\n', fp);
+  }
+
+  /* --- Main output --- */
+
+  for (i = 0; i < N(u.s); i++) {
+    fputs("  {", fp);
+    for (j = 0; j < N(u.s[i]); j++) {
+      fputs(" {", fp);
+      for (k = 0; k < N(u.s[i][j]); k++) {
+       fprintf(fp, " 0x%08lx", (unsigned long)u.s[i][j][k]);
+       if (k < N(u.s[i][j]) - 1) {
+         fputc(',', fp);
+         if (k % 4 == 3)
+           fputs(flags & f_ctab ? "\n     " : "\t\t\t\\\n     ", fp);
+       }
+      }
+      if (j < N(u.s[i]) - 1) {
+       fputs(flags & f_ctab ? " },\n\n   " :
+               " },\t\t\t\\\n\t\t\t\t\t\t\t\t\t\\\n   ", fp);
+      }
+    }
+    if (i < N(u.s) - 1) {
+      fputs(flags & f_ctab ? " } },\n\n" :
+           " } },\t\t\\\n\t\t\t\t\t\t\t\t\t\\\n", fp);
+    }
+  }
+
+  /* --- Done --- */
+
+  fputs(flags & f_ctab ? " } }\n} };\n" :
+       " } }\t\t\\\n} }\n", fp);
+  if (!(flags & f_ctab) && guard)
+    fputs("\n#endif\n", fp);
+
+  return (0);
+}
+
+/*----- That's all, folks -------------------------------------------------*/
diff --git a/hash/unihash.3 b/hash/unihash.3
new file mode 100644 (file)
index 0000000..28cb580
--- /dev/null
@@ -0,0 +1,197 @@
+.\" -*-nroff-*-
+.de VS
+.sp 1
+.RS
+.nf
+.ft B
+..
+.de VE
+.ft R
+.fi
+.RE
+.sp 1
+..
+.de hP
+.IP
+.ft B
+\h'-\w'\\$1\ 'u'\\$1\ \c
+.ft P
+..
+.ie t \{\
+.  ds ss \s8\u
+.  ds se \d\s0
+.  ds us \s8\d
+.  ds ue \u\s0
+.  ds *d \(*d
+.  ds >= \(>=
+.\}
+.el \{\
+.  ds ss ^
+.  ds se
+.  ds us _
+.  ds ue
+.  ds *d \fIdelta\fP
+.  ds >= >=
+.\}
+.TH unihash 3 "5 July 2003" "Straylight/Edgeware" "mLib utilities library"
+.SH NAME
+unihash \- simple and efficient universal hashing for hashtables
+.\" @unihash_setkey
+.\" @UNIHASH_INIT
+.\" @unihash_hash
+.\" @UNIHASH
+.\" @unihash
+.SH SYNOPSIS
+.nf
+.B "#include <mLib/unihash.h>"
+
+.B "unihash_info unihash_global;"
+
+.BI "void unihash_setkey(unihash_info *" i ", uint32 " k );
+.BI "uint32 UNIHASH_INIT(const unihash_info *" i );
+.BI "uint32 unihash_hash(const unihash_info *" i ", uint32 " a ,
+.BI "                    const void *" p ", size_t " sz );
+.BI "uint32 unihash(const unihash_info *" i ", const void *" p ", size_t " sz );
+.BI "uint32 UNIHASH(const unihash_info *" i ", const void *" p ", size_t " sz );
+.fi
+.SH DESCRIPTION
+The
+.B unihash
+system implements a simple and relatively efficient
+.IR "universal hashing family" .
+Using a such a universal hashing family means that it's provably
+difficult for an adversary to choose input data whose hashes collide,
+thus guaranteeing good average performance even on maliciously chosen
+data.
+.PP
+Unlike, say,
+.BR crc32 (3),
+the
+.B unihash
+function is
+.I keyed
+\- in addition to the data to be hashed, the function takes as input a
+32-bit key.  This key should be chosen at random each time the program
+runs.
+.SS "Preprocessing a key"
+Before use, a key must be
+.I preprocessed
+into a large (16K) table which is used by the main hashing functions.
+The preprocessing is done by
+.BR unihash_setkey :
+pass it a pointer to a
+.B unihash_info
+structure and the 32-bit key you've chosen, and it stores the table in
+the structure.
+.PP
+Objects of type
+.B unihash_info
+don't contain any pointers to other data and are safe to free when
+you've finished with them; or you can just allocate them statically or
+on the stack if that's more convenient.
+.SS "Hashing data"
+The function
+.B unihash_hash
+takes as input:
+.TP
+.BI "const unihash_info *" i
+A pointer to the precomputed tables for a key.
+.TP
+.BI "uint32 " a
+An accumulator value.  This should be
+.BI UNIHASH_INIT( i )
+for the first chunk of a multi-chunk input, or the result of the
+previous
+.B unihash_hash
+call for subsequent chunks.
+.TP
+.BI "const void *" p
+A pointer to the start of a buffer containing this chunk of data.
+.TP
+.BI "size_t " sz
+The length of the chunk.
+.PP
+The function returns a new accumulator value, which is also the hash of
+the data so far. So, to hash multiple chunks of data, do something like
+.VS
+uint32 a = UNIHASH_INIT(i);
+a = unihash_hash(i, a, p_0, sz_0);
+a = unihash_hash(i, a, p_1, sz_1);
+/* ... */
+a = unihash_hash(i, a, p_n, sz_n);
+.VE
+The macro
+.B UNIHASH
+and function
+.B unihash
+are convenient interfaces to
+.B unihash_hash
+if you only wanted to hash one chunk.
+.SS "Global hash info table"
+There's no problem with using the same key for several purposes, as long
+as it's secret from all of your adversaries.  Therefore, there is a
+global
+.B unihash_info
+table define, called
+.BR unihash_global .
+This initially contains information for a fixed key which the author
+chose at random, but if you need to you can set a different key into it
+.I before
+it gets used to hash any data (otherwise your hash tables will become
+messed up).
+.SS "Theoretical issues"
+The hash function implemented by
+.B unihash
+is
+.RI ( l \ +\ 1)/2\*(ss32\*(se-almost
+XOR-universal, where
+.I l
+is the length (in bytes) of the longest string you hash.  That means
+that, for any pair of strings
+.I x
+and
+.I y
+and any 32-bit value \*(*d, the probability taken over all choices of the
+key
+.I k
+that
+.IR H\*(usk\*(ue ( x )\  \c
+.BR xor \c
+.RI \  H\*(usk\*(ue ( y )\ =\ \*(*d
+is no greater than
+.RI ( l \ +\ 1)/2\*(ss32\*(se.
+.PP
+This fact is proven in the header file, but it requires more
+sophisticated typesetting than is available here.
+.PP
+The function evaluates a polynomial over GF(2\*(ss32\*(se) whose
+coefficients are the bytes of the message and whose variable is the key.
+Details are given in the header file.
+.PP
+For best results, you should choose the key as a random 32-bit number
+each time your program starts.  Choosing a different key for different
+hashtables isn't necessary.  It's probably a good idea to avoid the keys
+0 and 1.  This raises the collision bound to
+.RI ( l \ +\ 1)/(2\*(ss32\*(se\ \-\ 2)
+(which isn't a significant increase) but eliminates keys for which the
+hash's behaviour is particularly poor.
+.PP
+In tests,
+.B unihash
+actually performed better than
+.BR crc32 ,
+so if you want to just use it as a fast-ish hash with good statistical
+properties, choose some fixed key
+.IR k \ \*(>=\ 2.
+.PP
+We emphasize that the proof of this function's collision behaviour is
+.I not
+dependent on any unproven assumptions (unlike many `proofs' of
+cryptographic security, which actually reduce the security of some
+construction to the security of its components).  It's just a fact.
+.SH SEE ALSO
+.BR unihash-mkstatic (3),
+.BR crc32 (3),
+.BR mLib (3).
+.SH AUTHOR
+Mark Wooding (mdw@distorted.org.uk).
diff --git a/hash/unihash.c b/hash/unihash.c
new file mode 100644 (file)
index 0000000..c5ccd7e
--- /dev/null
@@ -0,0 +1,148 @@
+/* -*-c-*-
+ *
+ * Simple and efficient universal hashing for hashtables
+ *
+ * (c) 2003 Straylight/Edgeware
+ */
+
+/*----- Licensing notice --------------------------------------------------*
+ *
+ * This file is part of the mLib utilities library.
+ *
+ * mLib is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * mLib is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with mLib; if not, write to the Free
+ * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+ * MA 02111-1307, USA.
+ */
+
+/*----- Header files ------------------------------------------------------*/
+
+#include <stdlib.h>
+
+#include "macros.h"
+#include "unihash.h"
+
+/*----- Main code ---------------------------------------------------------*/
+
+/* --- @unihash_setkey@ --- *
+ *
+ * Arguments:  @unihash_info *i@ = where to store the precomputed tables
+ *             @uint32 k@ = the key to set, randomly chosen
+ *
+ * Returns:    ---
+ *
+ * Use:                Calculates the tables required for efficient hashing.
+ */
+
+static uint32 mul(uint32 x, uint32 y)
+{
+  uint32 z = 0;
+  while (y) {
+    if (y & 1) z ^= x;
+    if (x & (1 << 31))
+      x = U32(x << 1) ^ UNIHASH_POLY;
+    else
+      x = U32(x << 1);
+    y = U32(y >> 1);
+  }
+  return (z);
+}
+
+void unihash_setkey(unihash_info *i, uint32 k)
+{
+  size_t a;
+  size_t b;
+  uint32 x = 1;
+
+  for (a = 0; a < UNIHASH_NBATCH; a++) {
+    x = mul(x, k);
+    for (b = 0; b < 256; b++) {
+      i->s[a][0][b] = mul(x, b <<  0);
+      i->s[a][1][b] = mul(x, b <<  8);
+      i->s[a][2][b] = mul(x, b << 16);
+      i->s[a][3][b] = mul(x, b << 24);
+    }
+  }
+}
+
+/* --- @unihash_hash@ --- *
+ *
+ * Arguments:  @const unihash_info *i@ = pointer to precomputed table
+ *             @uint32 a@ = @i->[0][0][1]@ or value from previous call
+ *             @const void *p@ = pointer to data to hash
+ *             @size_t sz@ = size of the data
+ *
+ * Returns:    Hash of data so far.
+ *
+ * Use:                Hashes data.  Call this as many times as needed.
+ */
+
+uint32 unihash_hash(const unihash_info *i, uint32 a,
+                   const void *p, size_t sz)
+{
+  const octet *pp = p;
+
+  STATIC_ASSERT(UNIHASH_NBATCH == 4, "Batch size doesn't match computation");
+
+#define FULLMULT(u, x)                                                 \
+  (i->s[u][0][U8((x) >>         0)] ^ i->s[u][1][U8((x) >>  8)] ^              \
+   i->s[u][2][U8((x) >> 16)] ^ i->s[u][3][U8((x) >> 24)]);
+
+#define BYTEMULT(u, x) i->s[u][0][x]
+
+  /* --- Do the main bulk in batches of %$n$% bytes --- *
+   *
+   * We have %$a$% and %$m_{n-1}, \ldots, m_1, m_0$%; we want
+   *
+   * %$a' = (a + m_{n-1}) k^n + m_{n-2} k^{n-1} + \cdots + m_1 k^2 + m_0 k$%
+   */
+
+  while (sz >= UNIHASH_NBATCH) {
+    a ^= *pp++;
+    a = FULLMULT(3, a);
+    a ^= BYTEMULT(2, *pp++);
+    a ^= BYTEMULT(1, *pp++);
+    a ^= BYTEMULT(0, *pp++);
+    sz -= UNIHASH_NBATCH;
+  }
+
+  /* --- The tail end is a smaller batch --- */
+
+  switch (sz) {
+    case  3: a ^= *pp++; a = FULLMULT(2, a); goto batch_2;
+    case  2: a ^= *pp++; a = FULLMULT(1, a); goto batch_1;
+    case  1: a ^= *pp++; a = FULLMULT(0, a); goto batch_0;
+    batch_2: a ^= BYTEMULT(1, *pp++);
+    batch_1: a ^= BYTEMULT(0, *pp++);
+    batch_0: break;
+  }
+
+  return (a);
+}
+
+/* --- @unihash@ --- *
+ *
+ * Arguments:  @const unihash_info *i@ = precomputed tables
+ *             @const void *p@ = pointer to data to hash
+ *             @size_t sz@ = size of the data
+ *
+ * Returns:    The hash value computed.
+ *
+ * Use:                All-in-one hashing function.  No faster than using the
+ *             separate calls, but more convenient.
+ */
+
+uint32 unihash(const unihash_info *i, const void *p, size_t sz)
+  { return (UNIHASH(i, p, sz)); }
+
+/*----- That's all, folks -------------------------------------------------*/
diff --git a/hash/unihash.h b/hash/unihash.h
new file mode 100644 (file)
index 0000000..7ea30b1
--- /dev/null
@@ -0,0 +1,186 @@
+/* -*-c-*-
+ *
+ * Simple and efficient universal hashing for hashtables
+ *
+ * (c) 2003 Straylight/Edgeware
+ */
+
+/*----- Licensing notice --------------------------------------------------*
+ *
+ * This file is part of the mLib utilities library.
+ *
+ * mLib is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * mLib is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with mLib; if not, write to the Free
+ * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+ * MA 02111-1307, USA.
+ */
+
+#ifndef MLIB_UNIHASH_H
+#define MLIB_UNIHASH_H
+
+#ifdef __cplusplus
+  extern "C" {
+#endif
+
+
+/*----- Concept -----------------------------------------------------------*
+ *
+ * Let %$\gf{q}$% be a finite field.  Choose an arbitrary %$k \inr \gf{q}$%.
+ * Let %$M$% be a message.  Injectively pad %$M$% and split it into blocks
+ * $m_{n-1}, m_{n-2}, \ldots, m_2, m_1, m_0$% in %$\gf{q}%.
+ * Then we compute
+ *
+ *   %$H_k(M) = k^{n+1} + \sum_{0\le i<n} m_i k^{i+1}.$%
+ *
+ * Note that %$H_0(M) = 0$% for all messages %$M$%.
+ *
+ * If we deal with messages at most %$\ell$% blocks long then %$H_k(\cdot)$%
+ * is %$(\ell + 1)/q$%-almost universal.  Moreover, if %$q = 2^f$% then
+ * %$H_k(\cdot)$% is %$(\ell + 1)/q$%-almost XOR-universal.
+ *
+ * Proof.  Let %$A$% and %$B$% be two messages, represented by
+ * %$a_{n-1}, \ldots, a_0$% and %$b_{m-1}, \ldots, b_0$% respectively; and
+ * choose any %$\delta \in \gf{q}$%.  We must bound the probability that
+ *
+ * %$k^{n+1} + a_{n-1} k^{n} + \cdots + a_1 k^2 + a_0 k - {}$%
+ *   %$k^{m+1} - b_{m-1} k^{m} - \cdots - b_1 k^2 - b_0 k = \delta$%.
+ *
+ * Firstly, we claim that if %$A$% and %$B$% are distinct, there is some
+ * nonzero coefficient of %$k$%.  For if %$n \ne m$% then, without loss of
+ * generality, let %$n > m$%, and hence the coefficient of %$k_n$% is
+ * nonzero.  Alternatively, if %$n = m$% then there must be some
+ * %$i \in \{ 0, \ldots, n - 1 \}$% with %$a_i \ne b_i$%, for otherwise the
+ * messages would be identical; but then the coefficient of %$k^{i+1}$% is
+ * %$a_i - b_i \ne 0$%.
+ *
+ * Hence we have a polynomial equation with degree at most %$\ell + 1$%;
+ * there must be at most %$\ell + 1$% solutions for %$k$%; but we choose
+ * %$k$% at random from a set of %$q$%; so the equation is true with
+ * probability at most %$(\ell + 1)/q$%.
+ *
+ * This function can be used as a simple MAC with provable security against
+ * computationally unbounded adversaries.  Simply XOR the hash with a random
+ * string indexed from a large random pad by some nonce sent with the
+ * message.  The probability of a forgery attempt being successful is then
+ * %$(\ell + 1)/2^t$%, where %$t$% is the tag length and %$\ell$% is the
+ * longest message permitted.
+ */
+
+/*----- Practicalities ----------------------------------------------------*
+ *
+ * We work in %$\gf{2^32}$%, represented as a field of polynomials modulo
+ * %$\texttt{104c11db7}_x$% (this is the standard CRC-32 polynomial).  Our
+ * blocks are bytes.
+ *
+ * The choice of a 32-bit hash is made for pragmatic reasons: we're never
+ * likely to actually want all 32 bits for a real hashtable anyway.  The
+ * truncation result is needed to keep us afloat with smaller tables.
+ *
+ * We compute hashes using a slightly unrolled version of Horner's rule,
+ * using the recurrence:
+ *
+ *   %$a_{i+b} = (a_i + m_i) k^b + m_{i+1} k^{b-1} + \cdots + m_{i+b-1} k$%
+ *
+ * which involves one full-width multiply and %$b - 1$% one-byte multiplies;
+ * the latter may be efficiently computed using a table lookup.  Start with
+ * %$a_0 = k$%.
+ *
+ * We precompute tables %$S[\cdot][\cdot][\cdot]$%, where
+ *
+ *   %$S[u][v][w] = k^{u+1} x^{8v} w$%
+ *     for %$0 \le u < b$%, %$0 \le v < 4$%, %$0 \le w < 256)$%.
+ *
+ * A one-byte multiply is one lookup; a full-width multiply is four lookups
+ * and three XORs.  The processing required is then %$b + 3$% lookups and
+ * %$b + 3$% XORs per batch, or %$(b + 3)/b$% lookups and XORs per byte, at
+ * the expense of %$4 b$% kilobytes of tables.  This compares relatively
+ * favorably with CRC32.  Indeed, in tests, this implementation with $b = 4$%
+ * is faster than a 32-bit CRC.
+ */
+
+/*----- Header files ------------------------------------------------------*/
+
+#include <stddef.h>
+
+#ifndef MLIB_BITS_H
+#  include "bits.h"
+#endif
+
+/*----- Data structures ---------------------------------------------------*/
+
+#define UNIHASH_NBATCH 4
+#define UNIHASH_POLY 0x04c11db7                /* From CRC32 */
+
+typedef struct unihash_info {
+  uint32 s[UNIHASH_NBATCH][4][256];    /* S-tables as described */
+} unihash_info;
+
+/*----- A global hash-info table ------------------------------------------*/
+
+extern unihash_info unihash_global;    /* Key this if you like */
+
+/*----- Functions provided ------------------------------------------------*/
+
+/* --- @unihash_setkey@ --- *
+ *
+ * Arguments:  @unihash_info *i@ = where to store the precomputed tables
+ *             @uint32 k@ = the key to set, randomly chosen
+ *
+ * Returns:    ---
+ *
+ * Use:                Calculates the tables required for efficient hashing.
+ */
+
+extern void unihash_setkey(unihash_info */*i*/, uint32 /*k*/);
+
+/* --- @unihash_hash@ --- *
+ *
+ * Arguments:  @const unihash_info *i@ = pointer to precomputed table
+ *             @uint32 a@ = @UNIHASH_INIT(i)@ or value from previous call
+ *             @const void *p@ = pointer to data to hash
+ *             @size_t sz@ = size of the data
+ *
+ * Returns:    Hash of data so far.
+ *
+ * Use:                Hashes data.  Call this as many times as needed.
+ */
+
+#define UNIHASH_INIT(i) ((i)->s[0][0][1]) /* %$k$% */
+
+extern uint32 unihash_hash(const unihash_info */*i*/, uint32 /*a*/,
+                          const void */*p*/, size_t /*sz*/);
+
+/* --- @unihash@ --- *
+ *
+ * Arguments:  @const unihash_info *i@ = precomputed tables
+ *             @const void *p@ = pointer to data to hash
+ *             @size_t sz@ = size of the data
+ *
+ * Returns:    The hash value computed.
+ *
+ * Use:                All-in-one hashing function.  No faster than using the
+ *             separate calls, but more convenient.
+ */
+
+#define UNIHASH(i, p, sz) (unihash_hash((i), UNIHASH_INIT((i)), (p), (sz)))
+
+extern uint32 unihash(const unihash_info */*i*/,
+                     const void */*p*/, size_t /*sz*/);
+
+/*----- That's all, folks -------------------------------------------------*/
+
+#ifdef __cplusplus
+  }
+#endif
+
+#endif
diff --git a/mLib.3 b/mLib.3
new file mode 100644 (file)
index 0000000..174a03e
--- /dev/null
+++ b/mLib.3
@@ -0,0 +1,306 @@
+.\" -*-nroff-*-
+.TH mLib 3 "7 July 1999" "Straylight/Edgeware" "mLib utilities library"
+.SH NAME
+mLib \- library of miscellaneous utilities
+.\" @mLib
+.SH DESCRIPTION
+The
+.B mLib
+library is a mixed bag of things which the author finds useful in large
+numbers of programs.  As a result, its structure is somewhat arbitrary,
+and it's accreted extra bits over time rather than actually being
+designed as a whole.  In the author's opinion this isn't too much of a
+hardship.
+.PP
+At the most granular level,
+.B mLib
+is split into `modules', each of which has its own header file and
+manual page.  Sometimes there are identifiable `chunks' of several
+modules which fit together as a whole.  Modules and chunks fit into
+`layers', each depending on the ones below it.  The header file for
+module
+.I foo
+would be put in
+.BR <mLib/ \c
+.IR foo \c
+.BR .h> .
+.PP
+This description is a bit abstract, and
+.BR mLib ,
+as a result of its history, doesn't fit it as well as I might like.
+Even so, it's not too bad a model really.
+.PP
+The rest of this section describes the various chunks and layers.
+.SS "Exception handling"
+Right at the bottom, there's a fairly primitive exception handling
+system.  It's provided by the
+.BR exc (3)
+module, and stands alone.  It's used mainly by the memory allocation
+modules to raise exceptions when there's no more memory to be had.
+.SS "Memory allocation"
+The
+.BR arena (3)
+module provides an abstraction of memory allocation.  By writing
+appropriate arena implementations, a client program can control where
+and how memory is allocated for various structures.
+.PP
+The
+.BR alloc (3)
+module provides simple veneers onto traditional memory allocation
+functions like
+.BR malloc (3)
+and
+.BR strdup (3)
+(although
+.B mLib
+doesn't actually depend on
+.B strdup
+being defined in the library) which raise exceptions when there's not
+enough memory left.  These work through the
+.B arena
+layer, so that the caller can control memory allocation.
+.PP
+The
+.BR sub (3)
+module handles efficient allocation of small blocks.  It allocates
+memory in relatively big chunks and divides the chunks up into small
+blocks before returning them.  It keeps lists of differently-sized
+blocks so allocation and freeing is fast.  The downside is that your
+code must know how big a block is when it's being freed.
+.PP
+The
+.B track
+module (not yet documented) is a simple memory allocation tracker.  It
+can be handy when trying to fix memory leaks.
+.PP
+The
+.BR pool (3)
+module maintains resource pools which can manage memory and other
+resources, all of the resources held in a pool being destroyed along
+with the pool itself.
+.SS "String handling"
+The
+.BR str (3)
+module provides some trivial string-manipulation functions which tend to
+be useful quite often.
+.PP
+The
+.BR dstr (3)
+module implements a dynamic string data type.  It works quite quickly
+and well, and is handy in security-sensitive programs, to prevent
+buffer-overflows.  Dynamic strings are used occasionally through the
+rest of the library, mainly as output arguments.
+.PP
+The
+.BR buf (3)
+module provides simple functions for reading and writing binary data to
+or from fixed-sized buffers.
+.PP
+The
+.BR dspool (3)
+module implements a `pool' of dynamic strings which saves lots of
+allocation and deallocation when a piece of code has high string
+turnover.
+.SS "Program identification and error reporting"
+The
+.BR quis (3)
+module remembers the name of the program and supplies it when asked.
+It's used in error messages and similar things.
+.PP
+The
+.BR report (3)
+module emits standard Unixy error messages.  It provides functions
+.B moan
+and
+.B die
+which the author uses rather a lot.
+.PP
+The
+.BR trace (3)
+module provides an interface for emitting tracing information with
+configurable verbosity levels.  It needs improving to be able to cope
+with outputting to the system log.
+.SS "Other data types"
+The
+.BR hash (3)
+module provides the basics for an extending hashtable implementation.
+Many different hashtable-based data structures can be constructed with
+little effort.
+.PP
+The
+.BR sym (3)
+module implements a rather good general-purpose extending hash table.
+Keys and values can be arbitrary data.  It is implemented using
+.BR hash (3).
+.PP
+The
+.BR atom (3)
+module implements
+.IR atoms ,
+which are essentially strings with the property that two atoms have the
+same address if and only if they have the same text, so they can be used
+for rapid string comparisons.  The
+.BR assoc (3)
+module implements a hash table which uses atoms as keys, thus saving
+time spent hashing and comparing hash keys, and the space used for the
+keys.
+.PP
+The
+.BR darray (3)
+module implements dynamically resizing arrays which support Perl-like
+stack operations efficiently.
+.SS "Miscellaneous utilities"
+The
+.BR crc32 (3)
+module calculates CRC values for strings.  It used to be used by the
+symbol table manager as a hash function.
+.PP
+The
+.BR unihash (3)
+module implements a simple but efficient universal hashing family.  This
+is a keyed hash function which provides security against an adversary
+choosing input to a hash table deliberately to cause collisions.
+.PP
+The
+.BR lock (3)
+module does POSIX
+.BR fcntl (2)-style
+locking with a timeout.
+.PP
+The
+.BR env (3)
+module manipulates environment variables stored in a hashtable, and
+converts between the hashtable and the standard array representation of
+a process environment.
+.PP
+The
+.BR fdflags (3)
+module manipulates file descriptor flags in a fairly painless way.
+.PP
+The
+.BR fwatch (3)
+module allows you to easily find out whether a file has changed since
+the last time you looked at it.
+.PP
+The
+.BR lbuf (3)
+module implements a `line buffer', which is an object that emits
+completed lines of text from an incoming asynchronous data stream.  It's
+remarkably handy in programs that want to read lines from pipes and
+sockets can't block while waiting for a line-end to arrive.  Similarly,
+the
+.BR pkbuf (3)
+module implements a `packet buffer', which waits for packets of given
+lengths to arrive before dispatching them to a handler.
+.PP
+The
+.BR tv (3)
+module provides some macros and functions for playing with
+.BR "struct timeval" .
+.PP
+The
+.BR bits (3)
+module defines some types and macros for playing with words as chunks of
+bits.  There are portable rotate and shift macros (harder than you'd
+think), and macros to do loading and storing in known-endian formats.
+values.
+.PP
+The
+.BR mdwopt (3)
+module implements a fairly serious options parser compatible with the
+GNU options parser.
+.PP
+The
+.BR testrig (3)
+module provides a generic structure for reading test vectors from files
+and running them through functions.  I mainly use it for testing
+cryptographic transformations of various kinds.
+.SS "Encoding and decoding"
+The
+.BR base64 (3)
+module does base64 encoding and decoding, as defined in RFC2045.  Base64
+encodes arbitrary binary data in a reliable way which is resistant to
+character-set transformations and other mail transport bogosity.  The
+.BR base32 (3)
+module does base32 encoding and decoding, as defined in RFC2938.  This
+is a mad format which is needed for sha1 URNs, for no good reason.  The
+.BR hex (3)
+module does hex encoding and decoding.
+.PP
+The
+.BR url (3)
+module does urlencoding and decoding, as defined in RFC1866.
+Urlencoding encodes arbitrary (but mostly text-like) name/value pairs as
+a text string containing no whitespace.
+.SS "Multiplexed I/O"
+The
+.BR sel (3)
+module provides a basis for doing nonblocking I/O in Unix systems.  It
+provides types and functions for receiving events when files are ready
+for reading or writing, and when timers expire.
+.PP
+The
+.BR conn (3)
+module implements nonblocking network connections in a way which fits in
+with the
+.B sel
+system.  It makes nonblocking connects pretty much trivial.
+.PP
+The
+.BR selbuf (3)
+module attaches to the
+.B sel
+system and sends an event when lines of text arrive from a file.  It's
+useful when reading text from a network connection.  Similarly,
+.BR selpk (3)
+sents events when packets of given sizes arrive from a file.
+.PP
+The
+.BR sig (3)
+module introduces signal handling into the multiplexed I/O world.
+Signals are queued until dispatched through the normal
+.B sel
+mechanism.
+.PP
+The
+.BR ident (3)
+module provides a nonblocking ident (RFC931) client.  The
+.BR bres (3)
+module does background hostname and address resolution.
+.SH "SEE ALSO"
+.BR alloc (3),
+.BR assoc (3),
+.BR atom (3),
+.BR base64 (3),
+.BR bits (3),
+.BR buf (3),
+.BR bres (3),
+.BR conn (3),
+.BR crc32 (3),
+.BR darray (3),
+.BR dspool (3),
+.BR dstr (3),
+.BR env (3),
+.BR exc (3),
+.BR fdflags (3),
+.BR fwatch (3),
+.BR hash (3),
+.BR ident (3),
+.BR lbuf (3),
+.BR lock (3),
+.BR mdwopt (3),
+.BR pkbuf (3),
+.BR quis (3),
+.BR report (3),
+.BR sel (3),
+.BR selbuf (3),
+.BR selpk (3),
+.BR sig (3),
+.BR str (3),
+.BR sub (3),
+.BR sym (3),
+.BR trace (3),
+.BR tv (3),
+.BR url (3).
+.SH AUTHOR
+Mark Wooding, <mdw@distorted.org.uk>
diff --git a/mLib.pc.in b/mLib.pc.in
new file mode 100644 (file)
index 0000000..3bfdacc
--- /dev/null
@@ -0,0 +1,11 @@
+prefix=@prefix@
+exec_prefix=@exec_prefix@
+libdir=@libdir@
+includedir=@includedir@
+
+Name: mLib
+Description: A library of miscellaneous stuff
+Version: @VERSION@
+Libs: -L${libdir} -lmLib
+Libs.private: @MLIB_LIBS@
+Cflags: -I${includedir}
diff --git a/mem/Makefile.am b/mem/Makefile.am
new file mode 100644 (file)
index 0000000..7beb771
--- /dev/null
@@ -0,0 +1,55 @@
+### -*-makefile-*-
+###
+### Build script for memory allocation
+###
+### (c) 2009 Straylight/Edgeware
+###
+
+###----- Licensing notice ---------------------------------------------------
+###
+### This file is part of the mLib utilities library.
+###
+### mLib is free software; you can redistribute it and/or modify
+### it under the terms of the GNU Library General Public License as
+### published by the Free Software Foundation; either version 2 of the
+### License, or (at your option) any later version.
+###
+### mLib is distributed in the hope that it will be useful,
+### but WITHOUT ANY WARRANTY; without even the implied warranty of
+### MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+### GNU Library General Public License for more details.
+###
+### You should have received a copy of the GNU Library General Public
+### License along with mLib; if not, write to the Free
+### Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+### MA 02111-1307, USA.
+
+include $(top_srcdir)/vars.am
+
+noinst_LTLIBRARIES      = libmem.la
+libmem_la_SOURCES       =
+
+###--------------------------------------------------------------------------
+### Component files.
+
+## Arena abstraction.
+pkginclude_HEADERS     += arena.h
+libmem_la_SOURCES      += arena.c
+LIBMANS                        += arena.3
+
+## Memory allocation with exceptions.
+pkginclude_HEADERS     += alloc.h
+libmem_la_SOURCES      += alloc.c
+LIBMANS                        += alloc.3
+
+## Slab allocator.
+pkginclude_HEADERS     += sub.h
+libmem_la_SOURCES      += sub.c
+LIBMANS                        += sub.3
+
+## Pool allocator.
+pkginclude_HEADERS     += pool.h
+libmem_la_SOURCES      += pool.c pool-file.c pool-sub.c
+LIBMANS                        += pool.3
+
+###----- That's all, folks --------------------------------------------------
diff --git a/mem/alloc.3 b/mem/alloc.3
new file mode 100644 (file)
index 0000000..06ad53d
--- /dev/null
@@ -0,0 +1,69 @@
+.\" -*-nroff-*-
+.TH alloc 3 "8 May 1999" "Straylight/Edgeware" "mLib utilities library"
+.\" @xmalloc
+.\" @xrealloc
+.\" @xstrdup
+.\" @xfree
+.\" @x_alloc
+.\" @x_strdup
+.\" @x_realloc
+.\" @x_free
+.SH NAME
+alloc \- mLib low-level memory allocation
+.SH SYNOPSIS
+.nf
+.B "#include <mLib/alloc.h>"
+
+.BI "void *x_alloc(arena *" a ", size_t " sz );
+.BI "char *x_strdup(arena *" a ", const char *" s );
+.BI "void *x_realloc(arena *" a ", void *" p ", size_t " sz ", size_t " osz );
+.BI "void x_free(arena *" a ", void *" p );
+
+.BI "void *xmalloc(size_t " sz );
+.BI "void *xrealloc(void *" p ", size_t " sz ", size_t " osz );
+.BI "char *xstrdup(const char *" s );
+.BI "void xfree(void *" p );
+.fi
+.SH DESCRIPTION
+These functions allocate and return blocks of memory.  If insufficient
+memory is available, an
+.B EXC_NOMEM
+exception is raised.
+.PP
+The functions
+.BR x_alloc ,
+.BR x_realloc ,
+.BR x_strdup
+and
+.BR x_free
+work with a given arena (see
+.BR arena (3)).
+.B x_alloc
+allocates a block of a given size;
+.B x_realloc
+resizes an allocated block;
+.B x_strdup
+allocates a copy of a null-terminated string; and
+.B x_free
+releases a block.
+.RB ( x_free
+is supplied for orthogonality's sake: it's equivalent to calling the
+.BR A_FREE (3)
+macro.)
+.PP
+The
+.BR xmalloc ,
+.BR xrealloc ,
+.BR xstrdup
+and
+.BR xfree
+macros are provided as a convenient interface to failsafe memory
+allocation from the current arena
+.BR arena_global (3).
+.SH "SEE ALSO"
+.BR arena (3),
+.BR exc (3),
+.BR mLib (3).
+.SH AUTHOR
+Mark Wooding, <mdw@distorted.org.uk>
+
diff --git a/mem/alloc.c b/mem/alloc.c
new file mode 100644 (file)
index 0000000..e71ead4
--- /dev/null
@@ -0,0 +1,171 @@
+/* -*-c-*-
+ *
+ * Memory allocation functions
+ *
+ * (c) 1998 Straylight/Edgeware
+ */
+
+/*----- Licensing notice --------------------------------------------------*
+ *
+ * This file is part of the mLib utilities library.
+ *
+ * mLib is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * mLib is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with mLib; if not, write to the Free
+ * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+ * MA 02111-1307, USA.
+ */
+
+/*----- Header files ------------------------------------------------------*/
+
+/* --- ANSI headers --- */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+/* --- Local headers --- */
+
+#include "alloc.h"
+#include "arena.h"
+#include "exc.h"
+
+/*----- Functions and macros ----------------------------------------------*/
+
+/* --- @x_alloc@ --- *
+ *
+ * Arguments:  @arena *a@ = pointer to underlying arena
+ *             @size_t sz@ = size of block to allocate
+ *
+ * Returns:    Pointer to allocated block.
+ *
+ * Use:                Allocates memory.  If there's not enough memory, the
+ *             exception @EXC_NOMEM@ is thrown.
+ */
+
+void *x_alloc(arena *a, size_t sz)
+{
+  void *p = A_ALLOC(a, sz);
+  if (!p)
+    THROW(EXC_NOMEM);
+  return (p);
+}
+
+/* --- @x_strdup@ --- *
+ *
+ * Arguments:  @arena *a@ = pointer to underlying arena
+ *             @const char *s@ = pointer to a string
+ *
+ * Returns:    Pointer to a copy of the string.
+ *
+ * Use:                Copies a string (like @strdup@ would, if it existed).  If
+ *             there's not enough memory, the exception @EXC_NOMEM@ is
+ *             thrown.
+ */
+
+char *x_strdup(arena *a, const char *s)
+{
+  size_t sz = strlen(s) + 1;
+  char *p = x_alloc(a, sz);
+  memcpy(p, s, sz);
+  return (p);
+}
+
+/* --- @x_realloc@ --- *
+ *
+ * Arguments:  @arena *a@ = pointer to underlying arena
+ *             @void *p@ = pointer to a block of memory
+ *             @size_t sz@ = new size desired for the block
+ *             @size_t osz@ = size of the old block
+ *
+ * Returns:    Pointer to the resized memory block (which is almost
+ *             certainly not in the same place any more).
+ *
+ * Use:                Resizes a memory block.  If there's not enough memory, the
+ *             exception @EXC_NOMEM@ is thrown.
+ */
+
+void *x_realloc(arena *a, void *p, size_t sz, size_t osz)
+{
+  p = A_REALLOC(a, p, sz, osz);
+  if (!p)
+    THROW(EXC_NOMEM);
+  return (p);
+}
+
+/* --- @x_free@ --- *
+ *
+ * Arguments:  @arena *a@ = pointer to underlying arena
+ *             @void *p@ = pointer to a block of memory.
+ *
+ * Returns:    ---
+ *
+ * Use:                Frees a block of memory.
+ */
+
+void (x_free)(arena *a, void *p) { x_free(a, p); }
+
+/*----- Old functions for the standard arena ------------------------------*/
+
+/* --- @xmalloc@ --- *
+ *
+ * Arguments:  @size_t sz@ = size of block to allocate
+ *
+ * Returns:    Pointer to allocated block.
+ *
+ * Use:                Allocates memory.  If there's not enough memory, the
+ *             exception @EXC_NOMEM@ is thrown.
+ */
+
+void *(xmalloc)(size_t sz) { return xmalloc(sz); }
+
+/* --- @xstrdup@ --- *
+ *
+ * Arguments:  @const char *s@ = pointer to a string
+ *
+ * Returns:    Pointer to a copy of the string.
+ *
+ * Use:                Copies a string (like @strdup@ would, if it existed).  If
+ *             there's not enough memory, the exception @EXC_NOMEM@ is
+ *             thrown.
+ */
+
+char *(xstrdup)(const char *s) { return xstrdup(s); }
+
+/* --- @xrealloc@ --- *
+ *
+ * Arguments:  @void *p@ = pointer to a block of memory
+ *             @size_t sz@ = new size desired for the block
+ *             @size_t osz@ = size of the old block
+ *
+ * Returns:    Pointer to the resized memory block (which is almost
+ *             certainly not in the same place any more).
+ *
+ * Use:                Resizes a memory block.  If there's not enough memory, the
+ *             exception @EXC_NOMEM@ is thrown.
+ */
+
+void *(xrealloc)(void *p, size_t sz, size_t osz)
+{ return xrealloc(p, sz, osz); }
+
+/* --- @xfree@ --- *
+ *
+ * Arguments:  @void *p@ = pointer to a block of memory.
+ *
+ * Returns:    ---
+ *
+ * Use:                Frees a block of memory.
+ */
+
+void (xfree)(void *p) { xfree(p); }
+
+/*----- That's all, folks -------------------------------------------------*/
diff --git a/mem/alloc.h b/mem/alloc.h
new file mode 100644 (file)
index 0000000..2349181
--- /dev/null
@@ -0,0 +1,165 @@
+/* -*-c-*-
+ *
+ * Memory allocation functions
+ *
+ * (c) 1998 Straylight/Edgeware
+ */
+
+/*----- Licensing notice --------------------------------------------------*
+ *
+ * This file is part of the mLib utilities library.
+ *
+ * mLib is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * mLib is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with mLib; if not, write to the Free
+ * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+ * MA 02111-1307, USA.
+ */
+
+#ifndef MLIB_ALLOC_H
+#define MLIB_ALLOC_H
+
+#ifdef __cplusplus
+  extern "C" {
+#endif
+
+/*----- Required header files ---------------------------------------------*/
+
+#include <stddef.h>
+
+#ifndef MLIB_ARENA_H
+#  include "arena.h"
+#endif
+
+/*----- Functions and macros ----------------------------------------------*/
+
+/* --- @x_alloc@ --- *
+ *
+ * Arguments:  @arena *a@ = pointer to underlying arena
+ *             @size_t sz@ = size of block to allocate
+ *
+ * Returns:    Pointer to allocated block.
+ *
+ * Use:                Allocates memory.  If there's not enough memory, the
+ *             exception @EXC_NOMEM@ is thrown.
+ */
+
+extern void *x_alloc(arena */*a*/, size_t /*sz*/);
+
+/* --- @x_strdup@ --- *
+ *
+ * Arguments:  @arena *a@ = pointer to underlying arena
+ *             @const char *s@ = pointer to a string
+ *
+ * Returns:    Pointer to a copy of the string.
+ *
+ * Use:                Copies a string (like @strdup@ would, if it existed).  If
+ *             there's not enough memory, the exception @EXC_NOMEM@ is
+ *             thrown.
+ */
+
+extern char *x_strdup(arena */*a*/, const char */*s*/);
+
+/* --- @x_realloc@ --- *
+ *
+ * Arguments:  @arena *a@ = pointer to underlying arena
+ *             @void *p@ = pointer to a block of memory
+ *             @size_t sz@ = new size desired for the block
+ *             @size_t osz@ = size of the old block
+ *
+ * Returns:    Pointer to the resized memory block (which is almost
+ *             certainly not in the same place any more).
+ *
+ * Use:                Resizes a memory block.  If there's not enough memory, the
+ *             exception @EXC_NOMEM@ is thrown.
+ */
+
+extern void *x_realloc(arena */*a*/, void */*p*/,
+                      size_t /*sz*/, size_t /*osz*/);
+
+/* --- @x_free@ --- *
+ *
+ * Arguments:  @arena *a@ = pointer to underlying arena
+ *             @void *p@ = pointer to a block of memory.
+ *
+ * Returns:    ---
+ *
+ * Use:                Frees a block of memory.
+ */
+
+extern void x_free(arena */*a*/, void */*p*/);
+#define x_free(a, p) A_FREE(a, p)
+
+/*----- Old functions for the standard arena ------------------------------*/
+
+/* --- @xmalloc@ --- *
+ *
+ * Arguments:  @size_t sz@ = size of block to allocate
+ *
+ * Returns:    Pointer to allocated block.
+ *
+ * Use:                Allocates memory.  If there's not enough memory, the
+ *             exception @EXC_NOMEM@ is thrown.
+ */
+
+extern void *xmalloc(size_t /*sz*/);
+#define xmalloc(sz) x_alloc(arena_global, (sz))
+
+/* --- @xstrdup@ --- *
+ *
+ * Arguments:  @const char *s@ = pointer to a string
+ *
+ * Returns:    Pointer to a copy of the string.
+ *
+ * Use:                Copies a string (like @strdup@ would, if it existed).  If
+ *             there's not enough memory, the exception @EXC_NOMEM@ is
+ *             thrown.
+ */
+
+extern char *xstrdup(const char */*s*/);
+#define xstrdup(p) x_strdup(arena_global, (p))
+
+/* --- @xrealloc@ --- *
+ *
+ * Arguments:  @void *p@ = pointer to a block of memory
+ *             @size_t sz@ = new size desired for the block
+ *             @size_t osz@ = size of the old block
+ *
+ * Returns:    Pointer to the resized memory block (which is almost
+ *             certainly not in the same place any more).
+ *
+ * Use:                Resizes a memory block.  If there's not enough memory, the
+ *             exception @EXC_NOMEM@ is thrown.
+ */
+
+extern void *xrealloc(void */*p*/, size_t /*sz*/, size_t /*osz*/);
+#define xrealloc(p, sz, osz) x_realloc(arena_global, (p), (sz), (osz))
+
+/* --- @xfree@ --- *
+ *
+ * Arguments:  @void *p@ = pointer to a block of memory.
+ *
+ * Returns:    ---
+ *
+ * Use:                Frees a block of memory.
+ */
+
+extern void xfree(void */*p*/);
+#define xfree(p) x_free(arena_global, (p))
+
+/*----- That's all, folks -------------------------------------------------*/
+
+#ifdef __cplusplus
+  }
+#endif
+
+#endif
diff --git a/mem/arena.3 b/mem/arena.3
new file mode 100644 (file)
index 0000000..2a36820
--- /dev/null
@@ -0,0 +1,127 @@
+.\" -*-nroff-*-
+.TH arena 3 "3 June 2000" "Straylight/Edgeware" "mLib utilities library"
+.SH "NAME"
+arena \- control of memory allocation
+.\" @arena_global
+.\" @arena_stdlib
+.\" @arena_fakemalloc
+.\" @a_alloc
+.\" @a_realloc
+.\" @a_free
+.\" @A_ALLOC
+.\" @A_REALLOC
+.\" @A_FREE
+.SH "SYNOPSIS"
+.nf
+.B "#include <mLib/arena.h>"
+
+.BI "arena *arena_global;"
+.BI "arena arena_stdlib;"
+
+.BI "void *arena_fakerealloc(arena *" a ", void *" p ,
+.BI "                        size_t " sz ", size_t " osz );
+
+.BI "void *a_alloc(arena *" a ", size_t " sz );
+.BI "void *a_realloc(arena *" a ", void *" p ", size_t " sz ", size_t " osz );
+.BI "void a_free(arena *" a );
+
+.BI "void *A_ALLOC(arena *" a ", size_t " sz );
+.BI "void *A_REALLOC(arena *" a ", void *" p ", size_t " sz ", size_t " osz );
+.BI "void A_FREE(arena *" a );
+.fi
+.SH "DESCRIPTION"
+An
+.I arena
+is a place from which blocks of memory may be allocated and freed.  The
+basic
+.B mLib
+library provides a single standard arena,
+.BR arena_stdlib ,
+which uses the usual
+.BR malloc (3)
+and
+.BR free (3)
+calls.  The global variable
+.B arena_global
+is a pointer to a `current' arena, which is a good choice to use if
+you can't think of anything better.
+.PP
+The macros
+.BR A_ALLOC ,
+.B A_REALLOC
+and
+.B A_FREE
+behave like the standard C functions
+.BR malloc (3),
+.BR realloc (3)
+and
+.BR free (3),
+allocating, resizing and releasing blocks from a given arena.  There are
+function-call equivalents with lower-case names too.  For a more
+convenient interface to memory allocation, see
+.BR alloc (3).
+.PP
+.B Note:
+The
+.B realloc
+function has an extra argument
+.I osz
+specifying the old size of the block.  This is for the benefit of arena
+handlers which can't easily find the old block's size.
+.PP
+.SS "Defining new arenas"
+An
+.B arena
+is a structure containing a single member,
+.BR ops ,
+which is a pointer to a structure of type
+.BR arena_ops .
+The
+.B arena
+structure may be followed in memory by data which is used by the arena's
+manager to maintain its state.
+.PP
+The
+.B arena_ops
+table contains function pointers which are called to perform various
+memory allocation tasks:
+.TP
+.BI "void *(*" alloc ")(arena *" a ", size_t " sz );
+Allocates a block of memory, of at least
+.I sz
+bytes in size, appropriately aligned, and returns its address.
+.nf
+.TP
+.BI "void *(*" realloc ")(arena *" a ", void *" p ", size_t " sz ", size_t " osz );
+.fi
+Resizes the block pointed to by
+.IR p ,
+with
+.I osz
+interesting bytes in it,
+so that it is at least
+.I sz
+bytes long.  You can use
+.B arena_fakerealloc
+here, to fake resizing by allocating, copying and freeing, if your arena
+doesn't make doing something more efficient easy.
+.TP
+.BI "void (*" free ")(arena *" a ", void *" p );
+Frees the block pointed to by
+.IR p .
+.TP
+.BI "void (*" purge ")(arena *" a );
+Frees all blocks in the arena.  Used when the arena is being destroyed.
+.PP
+The behaviour of the
+.IR alloc ,
+.I realloc
+and
+.I free
+calls with respect to null pointers and zero-sized blocks is as
+specified by the ANSI C standard.
+.SH "SEE ALSO"
+.BR alloc (3),
+.BR mLib (3).
+.SH AUTHOR
+Mark Wooding, <mdw@distorted.org.uk>
diff --git a/mem/arena.c b/mem/arena.c
new file mode 100644 (file)
index 0000000..3259dc8
--- /dev/null
@@ -0,0 +1,81 @@
+/* -*-c-*-
+ *
+ * Abstraction for memory allocation arenas
+ *
+ * (c) 2000 Straylight/Edgeware
+ */
+
+/*----- Licensing notice --------------------------------------------------*
+ *
+ * This file is part of the mLib utilities library.
+ *
+ * mLib is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * mLib is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with mLib; if not, write to the Free
+ * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+ * MA 02111-1307, USA.
+ */
+
+/*----- Header files ------------------------------------------------------*/
+
+#include <stdlib.h>
+#include <string.h>
+
+#include "arena.h"
+
+/*----- The standard arena ------------------------------------------------*/
+
+static void *_alloc(arena *a, size_t sz) { return malloc(sz); }
+static void *_realloc(arena *a, void *p, size_t sz, size_t osz)
+  { return realloc(p, sz); }
+static void _free(arena *a, void *p) { free(p); }
+
+static arena_ops stdlib_ops = { _alloc, _realloc, _free, 0 };
+arena arena_stdlib = { &stdlib_ops };
+
+/*----- Global variables --------------------------------------------------*/
+
+arena *arena_global = &arena_stdlib;
+
+/*----- Main code ---------------------------------------------------------*/
+
+/* --- @arena_fakerealloc@ --- *
+ *
+ * Arguments:  @arena *a@ = pointer to arena block
+ *             @void *p@ = pointer to memory block to resize
+ *             @size_t sz@ = size desired for the block
+ *             @size_t osz@ = size of the old block
+ *
+ * Returns:    ---
+ *
+ * Use:                Standard fake @realloc@ function, for use if you don't
+ *             support @realloc@ properly.
+ */
+
+void *arena_fakerealloc(arena *a, void *p, size_t sz, size_t osz)
+{
+  void *q = A_ALLOC(a, sz);
+  if (!q)
+    return (0);
+  memcpy(q, p, sz > osz ? osz : sz);
+  A_FREE(a, p);
+  return (q);
+}
+
+/* --- Function equivalents of the macros --- */
+
+void *a_alloc(arena *a, size_t sz) { return (A_ALLOC(a, sz)); }
+void *a_realloc(arena *a, void *p, size_t sz, size_t osz)
+{ return A_REALLOC(a, p, sz, osz); }
+void a_free(arena *a, void *p) { A_FREE(a, p); }
+
+/*----- That's all, folks -------------------------------------------------*/
diff --git a/mem/arena.h b/mem/arena.h
new file mode 100644 (file)
index 0000000..82f6cd6
--- /dev/null
@@ -0,0 +1,96 @@
+/* -*-c-*-
+ *
+ * Abstraction for memory allocation arenas
+ *
+ * (c) 2000 Straylight/Edgeware
+ */
+
+/*----- Licensing notice --------------------------------------------------*
+ *
+ * This file is part of the mLib utilities library.
+ *
+ * mLib is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * mLib is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with mLib; if not, write to the Free
+ * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+ * MA 02111-1307, USA.
+ */
+
+#ifndef MLIB_ARENA_H
+#define MLIB_ARENA_H
+
+#ifdef __cplusplus
+  extern "C" {
+#endif
+
+/*----- Header files ------------------------------------------------------*/
+
+#include <stdlib.h>
+
+/*----- Data structures ---------------------------------------------------*/
+
+/* --- An arena structure --- */
+
+typedef struct arena {
+  const struct arena_ops *ops;
+} arena;
+
+typedef struct arena_ops {
+  void *(*alloc)(arena */*a*/, size_t /*sz*/);
+  void *(*realloc)(arena */*a*/, void */*p*/, size_t /*sz*/, size_t /*osz*/);
+  void (*free)(arena */*a*/, void */*p*/);
+  void (*purge)(arena */*a*/);
+} arena_ops;
+
+/*----- Global variables --------------------------------------------------*/
+
+extern arena *arena_global;            /* Standard global arena */
+extern arena arena_stdlib;             /* Arena based on @malloc@/@free@ */
+
+/*----- Functions provided ------------------------------------------------*/
+
+/* --- @arena_fakerealloc@ --- *
+ *
+ * Arguments:  @arena *a@ = pointer to arena block
+ *             @void *p@ = pointer to memory block to resize
+ *             @size_t sz@ = size desired for the block
+ *             @size_t osz@ = size of the old block
+ *
+ * Returns:    ---
+ *
+ * Use:                Standard fake @realloc@ function, for use if you don't
+ *             support @realloc@ properly.
+ */
+
+extern void *arena_fakerealloc(arena */*a*/, void */*p*/,
+                              size_t /*sz*/, size_t /*osz*/);
+
+/* --- Useful macros --- */
+
+#define A_ALLOC(a, sz) (((a)->ops->alloc)((a), (sz)))
+#define A_REALLOC(a, p, sz, osz) (((a)->ops->realloc)((a), (p), (sz), (osz)))
+#define A_FREE(a, p) (((a)->ops->free)((a), (p)))
+
+/* --- Simple function equivalents --- */
+
+extern void *a_alloc(arena */*a*/, size_t /*sz*/);
+extern void *a_realloc(arena */*a*/, void */*p*/,
+                      size_t /*sz*/, size_t /*osz*/);
+extern void a_free(arena */*a*/, void */*p*/);
+
+/*----- That's all, folks -------------------------------------------------*/
+
+#ifdef __cplusplus
+  }
+#endif
+
+#endif
diff --git a/mem/pool-file.c b/mem/pool-file.c
new file mode 100644 (file)
index 0000000..037d6fe
--- /dev/null
@@ -0,0 +1,82 @@
+/* -*-c-*-
+ *
+ * File handles in resource pools
+ *
+ * (c) 2000 Straylight/Edgeware
+ */
+
+/*----- Licensing notice --------------------------------------------------*
+ *
+ * This file is part of the mLib utilities library.
+ *
+ * mLib is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * mLib is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with mLib; if not, write to the Free
+ * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+ * MA 02111-1307, USA.
+ */
+
+/*----- Header files ------------------------------------------------------*/
+
+#include <stdio.h>
+
+#include "pool.h"
+
+/*----- Main code ---------------------------------------------------------*/
+
+/* --- @pool_fopen@ --- *
+ *
+ * Arguments:  @pool *p@ = pointer to a pool
+ *             @const char *file@ = name of the file to open
+ *             @const char *how@ = string specifying opening parameters
+ *
+ * Returns:    A pointer to a pool resource containing an open file handle,
+ *             or null if the file open filed.
+ *
+ * Use:                Opens a file so that it will be freed again when a pool is
+ *             destroyed.
+ */
+
+static void pf_destroy(pool_resource *r) { pool_fclose((pool_file *)r); }
+
+pool_file *pool_fopen(pool *p, const char *file, const char *how)
+{
+  FILE *fp;
+  pool_file *pf;
+
+  if ((fp = fopen(file, how)) == 0)
+    return (0);
+  pf = pool_alloc(p, sizeof(pool_file));
+  POOL_ADD(p, &pf->r, pf_destroy);
+  pf->fp = fp;
+  return (pf);
+}
+
+/* --- @pool_fclose@ --- *
+ *
+ * Arguments:  @pool_file *pf@ = pointer to a file resource
+ *
+ * Returns:    The response from the @fclose@ function.
+ *
+ * Use:                Closes a file.  It is not an error to close a file multiple
+ *             times.
+ */
+
+int pool_fclose(pool_file *pf)
+{
+  if (!pf->r.destroy)
+    return (0);
+  pf->r.destroy = 0;
+  return (fclose(pf->fp));
+}
+
+/*----- That's all, folks -------------------------------------------------*/
diff --git a/mem/pool-sub.c b/mem/pool-sub.c
new file mode 100644 (file)
index 0000000..f745960
--- /dev/null
@@ -0,0 +1,53 @@
+/* -*-c-*-
+ *
+ * Subarenas in resource pools
+ *
+ * (c) 2000 Straylight/Edgeware
+ */
+
+/*----- Licensing notice --------------------------------------------------*
+ *
+ * This file is part of the mLib utilities library.
+ *
+ * mLib is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * mLib is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with mLib; if not, write to the Free
+ * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+ * MA 02111-1307, USA.
+ */
+
+/*----- Header files ------------------------------------------------------*/
+
+#include "pool.h"
+#include "sub.h"
+
+/*----- Main code ---------------------------------------------------------*/
+
+/* --- @pool_subarena@ --- *
+ *
+ * Arguments:  @pool *p@ = pointer to the pool
+ *
+ * Returns:    A subarena built from the pool's memory allocator.
+ *
+ * Use:                Creates a suballocation arena attached to a pool.  The arena
+ *             and all of its memory will be freed when the pool is
+ *             destroyed.
+ */
+
+subarena *pool_subarena(pool *p)
+{
+  subarena *sa = pool_alloc(p, sizeof(subarena));
+  subarena_create(sa, &p->a);
+  return (sa);
+}
+
+/*----- That's all, folks -------------------------------------------------*/
diff --git a/mem/pool.3 b/mem/pool.3
new file mode 100644 (file)
index 0000000..4881bf1
--- /dev/null
@@ -0,0 +1,176 @@
+.\" -*-nroff-*-
+.de VS
+.sp 1
+.RS
+.nf
+.ft B
+..
+.de VE
+.ft R
+.fi
+.RE
+.sp 1
+..
+.TH pool 3 "7 July 2000" "Straylight/Edgeware" "mLib utilities library"
+.SH "NAME"
+pool \- resource pool management
+.\" @pool_alloc
+.\" @pool_strdup
+.\" @pool_init
+.\" @pool_create
+.\" @pool_destroy
+.\" @pool_sub
+.\" @pool_add
+.\" @POOL_ADD
+.\" @pool_fopen
+.\" @pool_fclose
+.\" @pool_subarena
+.SH "SYNOPSIS"
+.nf
+.B "#include <mLib/pool.h>"
+
+.BI "void pool_init(pool *" p ", arena *" a );
+.BI "pool *pool_create(arena *" a );
+.BI "pool *pool_sub(pool *" p );
+.BI "void pool_destroy(pool *" p );
+.BI "void pool_add(pool *" p ", pool_resource *" r ,
+.BI "              void (*" dfn ")(pool_resource *" r ));
+.BI "void *pool_alloc(pool *" p ", size_t " sz );
+.BI "char *pool_strdup(pool *" p ", const char *" s );
+.BI "pool_file *pool_fopen(pool *" p ", const char *" file ", const char *" how );
+.BI "int pool_fclose(pool_file *" pf );
+.BI "subarena *pool_subarena(pool *" p );
+
+.BI "void POOL_ADD(pool *" p ", pool_resource *" r ,
+.BI "              void (*" dfn ")(pool_resource *" r ));
+.fi
+.SH "DESCRIPTION"
+.SS "Overview"
+A
+.I "resource pool"
+is a collection of resources (e.g., memory, files) which may be disposed
+of simultaneously.
+.PP
+A pool may be a
+.IR "root pool" ,
+in which case it stands on its own, or it may be a
+.IR "subpool"
+of another pool (which may in turn either be a root pool or a subpool of
+another).
+.PP
+Pools manage memory efficiently.  Memory is allocated in large chunks
+from an
+.BR arena (3),
+and given out as necessary to callers.  There is no way of freeing
+memory dynamically; instead, the memory allocated by a pool is freed
+when the pool is destroyed.  While allocation is rapid, there is waste
+because the allocator has to ensure that blocks are properly aligned.
+Since pools offer an arena interface, it is possible to build a
+.BR subarena (3)
+over them.  This also enables memory in the subarena to be reclaimed
+when the pool is destroyed.
+.PP
+Other resources (e.g., file handles) may be added to the pool.  The pool
+will automatically release any resources it has when it's destroyed.
+Attaching resources to an appropriate pool can therefore be a useful way
+of avoiding memory leaks.
+.SS "Creating and destroying pools"
+A new root pool is created using
+.BR pool_create ,
+passing it an arena from which it can allocate large memory blocks.
+Alternatively, you can allocate a
+.B pool
+structure from somewhere and initialize it by passing its address and an
+arena to
+.BR pool_init .
+.PP
+A subpool is created by calling
+.BR pool_sub ,
+naming the parent pool.
+.PP
+Pools are destroyed by passing them to
+.BR pool_destroy .
+Pools created by
+.B pool_create
+are completely destroyed, since the memory containing the pool structure
+is allocated from the pool itself.  Subpools and pools allocated by the
+caller and initialized by
+.BR pool_init ,
+on the other hand, are
+allocated from a parent pool, and may be reused after being `destroyed'.
+.SS "Memory allocation"
+Memory is allocated from a pool by calling
+.BR pool_alloc ,
+passing it the pool and the size of memory requested.  There is an
+interface for copying strings,
+.BR pool_strdup ,
+since this is a common operation.  Note that there is no
+.BR pool_free :
+if this is important, either use the pool's arena
+.B p->pa
+directly or create a subpool.
+.PP
+A pool provides an
+.BR arena (3)
+interface,
+.BR p->a ,
+which can be passed to other components to cause them to use the pool
+for memory allocation.
+.SS "Other resources"
+Pool resources have a header of type
+.B pool_resource
+with the structure:
+.VS
+typedef struct pool_resource {
+  struct pool_resource *next;
+  void (*destroy)(struct pool_resource */*r*/);
+} pool_resource;
+.VE
+Resources are added to the pool by passing a pointer to the pool, the
+resource block and a destruction function to
+.BR pool_add .
+.PP
+If your resource is freed before the pool is destroyed, manually zero
+the
+.B destroy
+field in the resource header to let the pool manager know not to free
+the resource again.
+.PP
+It's usual to allocate the resource structures from the pool's arena so
+that they're automatically freed when the pool is destroyed.
+.PP
+A
+.BR subarena (3)
+may be created for a particular pool by calling
+.BR pool_subarena .
+The subarena and its contents will be freed automatically when the pool
+is destroyed.
+.PP
+Files may be opened and registered with a pool by
+.BR pool_fopen :
+the
+.I pool
+argument specifies which pool, and the
+.I file
+and
+.I how
+arguments are passed to the standard
+.BR fopen (3)
+function.  The return value is a pointer to a
+.B pool_file
+structure, containing a member
+.B fp
+which is the actual file handle.  Don't call
+.B fclose
+directly on the file handle: instead pass the whole structure to
+.B pool_fclose
+which will ensure that it doesn't get closed twice by accident.  It's
+advisable to close files by hand, to prevent the process from running
+out; it's just not a disaster if you forget by accident.
+.SH "SEE ALSO"
+.BR alloc (3),
+.BR arena (3),
+.BR mLib (3),
+.BR subarena (3).
+.SH AUTHOR
+Mark Wooding, <mdw@distorted.org.uk>
diff --git a/mem/pool.c b/mem/pool.c
new file mode 100644 (file)
index 0000000..69e852d
--- /dev/null
@@ -0,0 +1,278 @@
+/* -*-c-*-
+ *
+ * Resource pool handling
+ *
+ * (c) 2000 Straylight/Edgeware
+ */
+
+/*----- Licensing notice --------------------------------------------------*
+ *
+ * This file is part of the mLib utilities library.
+ *
+ * mLib is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * mLib is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with mLib; if not, write to the Free
+ * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+ * MA 02111-1307, USA.
+ */
+
+/*----- Header files ------------------------------------------------------*/
+
+#include <string.h>
+
+#include "align.h"
+#include "alloc.h"
+#include "arena.h"
+#include "pool.h"
+
+/*----- Main code ---------------------------------------------------------*/
+
+/* --- @doalloc@ --- *
+ *
+ * Arguments:  @arena *a@ = pointer to arena to allocate memory from
+ *             @pool_chunk **cc@ = pointer to chunk list
+ *             @size_t sz@ = size of memory wanted
+ *
+ * Returns:    Pointer to the allocated block.
+ *
+ * Use:                The basic allocator for resource pools.  This is also used
+ *             during pool creation, hence the slightly bizarre interface.
+ */
+
+static void *doalloc(arena *a, pool_chunk **cc, size_t sz)
+{
+  pool_chunk *c;
+  void *p;
+  size_t csz, ssz;
+
+  /* --- See if there's enough space --- *
+   *
+   * The chunks are sorted by available space, so if there's not enough space
+   * in the first chunk there isn't enough space anywhere.
+   */
+
+  ALIGN(sz);
+  c = *cc;
+  if (c && c->left >= sz) {
+    p = c->p;
+    c->p += sz;
+    c->left -= sz;
+    *cc = c->next;
+  }
+
+  /* --- Failed to find anything --- *
+   *
+   * I must allocate a new block from the arena, then.
+   */
+
+  else {
+    ssz = sizeof(pool_chunk);
+    ALIGN(ssz);
+    csz = (ssz + sz + POOL_CHUNKSZ - 1); csz -= csz % POOL_CHUNKSZ;
+    c = x_alloc(a, csz);
+    p = (char *)c + ssz;
+    c->p = (char *)p + sz;
+    c->left = csz - ssz - sz;
+  }
+
+  /* --- Move this chunk in the list so that it's sorted --- */
+
+  while (*cc && (*cc)->left > c->left)
+    cc = &(*cc)->next;
+  c->next = *cc;
+  *cc = c;
+
+  /* --- Done --- */
+
+  return (p);
+}
+
+/* --- @pool_alloc@ --- *
+ *
+ * Arguments:  @pool *p@ = pool to allocate from
+ *             @size_t sz@ = size of block wanted
+ *
+ * Returns:    Pointer to the requested block.
+ *
+ * Use:                Allocates memory from a resource pool.  Memory is never freed
+ *             from pools: it is released when the pool is destroyed.
+ */
+
+void *pool_alloc(pool *p, size_t sz)
+{
+  return (doalloc(p->pa, &p->c, sz));
+}
+
+/* --- @pool_strdup@ --- *
+ *
+ * Arguments:  @pool *p@ = pool to allocate from
+ *             @const char *s@ = pointer to string
+ *
+ * Returns:    A pointer to a copy of the string.
+ *
+ * Use:                Allocates a copy of a string.
+ */
+
+char *pool_strdup(pool *p, const char *s)
+{
+  size_t sz = strlen(s) + 1;
+  char *pp = doalloc(p->pa, &p->c, sz);
+  memcpy(pp, s, sz);
+  return (pp);
+}
+
+/* --- Arena operations --- */
+
+static void *palloc(arena *a, size_t sz)
+{
+  pool *p = (pool *)a;
+  return (doalloc(p->pa, &p->c, sz));
+}
+
+static void pfree(arena *a, void *p) { return; } /* Trivial */
+
+static arena_ops pool_ops = { palloc, arena_fakerealloc, pfree, 0 };
+
+/* --- @pool_init@ --- *
+ *
+ * Arguments:  @pool *p@ = pointer to the pool structure to initialize
+ *             @arena *a@ = pointer to an arena to allocate memory from
+ *
+ * Returns:    ---
+ *
+ * Use:                Initializes a chunk of memory as a resource pool which is not
+ *             a child of any other resource pool.
+ */
+
+void pool_init(pool *p, arena *a)
+{
+  p->a.ops = &pool_ops;
+  p->c = 0;
+  p->r = 0;
+  p->pa = a;
+}
+
+/* --- @pool_create@ --- *
+ *
+ * Arguments:  @arena *a@ = pointer to an arena to allocate memory from
+ *
+ * Returns:    A newly created resource pool.
+ *
+ * Use:                Creates a resource pool which is not a child of any other
+ *             resource pool.
+ */
+
+pool *pool_create(arena *a)
+{
+  pool_chunk *c = 0;
+  pool *p = doalloc(a, &c, sizeof(pool));
+  pool_init(p, a);
+  p->c = c;
+  return (p);
+}
+
+/* --- @pool_destroy@ --- *
+ *
+ * Arguments:  @pool *p@ = pointer to pool to destroy
+ *
+ * Returns:    ---
+ *
+ * Use:                Destroys a pool, freeing all of the resources within it.  If
+ *             this is a pool created by @pool_create@, its memory will be
+ *             deallocated; if it's a subpool or it was initialized by
+ *             @pool_init@, it is emptied and can be used again.
+ */
+
+void pool_destroy(pool *p)
+{
+  pool_resource *r, *rr;
+  arena *a;
+  pool_chunk *c, *cc;
+
+  /* --- Dispose of all of the resources --- */
+
+  r = p->r;
+  while (r) {
+    rr = r->next;
+    if (r->destroy)
+      r->destroy(r);
+    r = rr;
+  }
+  p->r = 0;
+
+  /* --- Free all of the memory --- *
+   *
+   * Since root pools are allocated in their own memory, this will free the
+   * root pool block.  Subpools are allocated in their parent's memory, so
+   * the pool block itself will be left around.
+   */
+
+  a = p->pa;
+  c = p->c;
+  p->c = 0;
+  while (c) {
+    cc = c->next;
+    x_free(a, c);
+    c = cc;
+  }
+}
+
+/* --- @pool_add@ --- *
+ *
+ * Arguments:  @pool *p@ = pointer to pool to add the resource to
+ *             @pool_resource *r@ = pointer to resource block
+ *             @void (*dfn)(pool_resource *r)@ = destruction function
+ *
+ * Returns:    ---
+ *
+ * Use:                Adds a resource to a pool.
+ */
+
+void pool_add(pool *p, pool_resource *r, void (*dfn)(pool_resource *r))
+{
+  POOL_ADD(p, r, dfn);
+}
+
+/* --- @pool_sub@ --- *
+ *
+ * Arguments:  @pool *p@ = pointer to parent pool
+ *
+ * Returns:    A new child pool of the parent.
+ *
+ * Use:                Creates a subpool.  The subpool can either be destroyed on
+ *             its own, or will be automatically destroyed at the same time
+ *             as the parent.
+ */
+
+typedef struct subpool {
+  pool_resource r;
+  pool p;
+} subpool;
+
+static void subpool_destroy(pool_resource *r)
+{
+  subpool *p = (subpool *)r;
+  pool_destroy(&p->p);
+}
+
+pool *pool_sub(pool *p)
+{
+  subpool *pp = pool_alloc(p, sizeof(subpool));
+  POOL_ADD(p, &pp->r, subpool_destroy);
+  pp->p.a.ops = &pool_ops;
+  pp->p.c = 0;
+  pp->p.r = 0;
+  pp->p.pa = p->pa;
+  return (&pp->p);
+}
+
+/*----- That's all, folks -------------------------------------------------*/
diff --git a/mem/pool.h b/mem/pool.h
new file mode 100644 (file)
index 0000000..5a843ab
--- /dev/null
@@ -0,0 +1,210 @@
+/* -*-c-*-
+ *
+ * Resource pool handling
+ *
+ * (c) 2000 Straylight/Edgeware
+ */
+
+/*----- Licensing notice --------------------------------------------------*
+ *
+ * This file is part of the mLib utilities library.
+ *
+ * mLib is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * mLib is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with mLib; if not, write to the Free
+ * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+ * MA 02111-1307, USA.
+ */
+
+#ifndef MLIB_POOL_H
+#define MLIB_POOL_H
+
+#ifdef __cplusplus
+  extern "C" {
+#endif
+
+/*----- Header files ------------------------------------------------------*/
+
+#include <stdio.h>
+
+#ifndef MLIB_ARENA_H
+#  include "arena.h"
+#endif
+
+#ifndef MLIB_SUB_H
+#  include "sub.h"
+#endif
+
+/*----- Data structures ---------------------------------------------------*/
+
+#define POOL_CHUNKSZ 65536
+
+typedef struct pool_chunk {
+  struct pool_chunk *next;             /* Next memory chunk in the chain */
+  char *p;                             /* Free area in this chunk */
+  size_t left;                         /* Amount of memory left */
+} pool_chunk;
+
+typedef struct pool_resource {
+  struct pool_resource *next;          /* Next resource in the chain */
+  void (*destroy)(struct pool_resource */*r*/); /* Destruction function */
+} pool_resource;
+
+typedef struct pool {
+  arena a;                             /* The arena for allocating memory */
+  pool_chunk *c;                       /* Pointer to memory chunk list */
+  pool_resource *r;                    /* Pointer to resource list */
+  arena *pa;                           /* Arena for real allocation */
+} pool;
+
+typedef struct pool_file {
+  pool_resource r;                     /* A pool resource record */
+  FILE *fp;                            /* The actual file handle */
+} pool_file;
+
+/*----- Basic pool management ---------------------------------------------*/
+
+/* --- @pool_alloc@ --- *
+ *
+ * Arguments:  @pool *p@ = pool to allocate from
+ *             @size_t sz@ = size of block wanted
+ *
+ * Returns:    Pointer to the requested block.
+ *
+ * Use:                Allocates memory from a resource pool.  Memory is never freed
+ *             from pools: it is released when the pool is destroyed.
+ */
+
+extern void *pool_alloc(pool */*p*/, size_t /*sz*/);
+
+/* --- @pool_strdup@ --- *
+ *
+ * Arguments:  @pool *p@ = pool to allocate from
+ *             @const char *s@ = pointer to string
+ *
+ * Returns:    A pointer to a copy of the string.
+ *
+ * Use:                Allocates a copy of a string.
+ */
+
+extern char *pool_strdup(pool */*p*/, const char */*s*/);
+
+/* --- @pool_create@ --- *
+ *
+ * Arguments:  @arena *a@ = pointer to an arena to allocate memory from
+ *
+ * Returns:    A newly created resource pool.
+ *
+ * Use:                Creates a resource pool which is not a child of any other
+ *             resource pool.
+ */
+
+extern pool *pool_create(arena */*a*/);
+
+/* --- @pool_destroy@ --- *
+ *
+ * Arguments:  @pool *p@ = pointer to pool to destroy
+ *
+ * Returns:    ---
+ *
+ * Use:                Destroys a pool, freeing all of the resources within it.  If
+ *             this is a root pool, its memory will be deallocated; if it's
+ *             a subpool, it is emptied and can be used again.
+ */
+
+extern void pool_destroy(pool */*p*/);
+
+/* --- @pool_sub@ --- *
+ *
+ * Arguments:  @pool *p@ = pointer to parent pool
+ *
+ * Returns:    A new child pool of the parent.
+ *
+ * Use:                Creates a subpool.  The subpool can either be destroyed on
+ *             its own, or will be automatically destroyed at the same time
+ *             as the parent.
+ */
+
+extern pool *pool_sub(pool */*p*/);
+
+/* --- @pool_add@ --- *
+ *
+ * Arguments:  @pool *p@ = pointer to pool to add the resource to
+ *             @pool_resource *r@ = pointer to resource block
+ *             @void (*dfn)(pool_resource *r)@ = destruction function
+ *
+ * Returns:    ---
+ *
+ * Use:                Adds a resource to a pool.
+ */
+
+#define POOL_ADD(p, rr, dfn) do {                                      \
+  pool *_p = (p);                                                      \
+  pool_resource *_r = (rr);                                            \
+  _r->next = _p->r;                                                    \
+  _r->destroy = dfn;                                                   \
+  _p->r = _r;                                                          \
+} while (0)
+
+extern void pool_add(pool */*p*/, pool_resource */*r*/,
+                    void (*/*dfn*/)(pool_resource */*r*/));
+
+/*----- Various simple resource types -------------------------------------*/
+
+/* --- @pool_fopen@ --- *
+ *
+ * Arguments:  @pool *p@ = pointer to a pool
+ *             @const char *file@ = name of the file to open
+ *             @const char *how@ = string specifying opening parameters
+ *
+ * Returns:    A pointer to a pool resource containing an open file handle,
+ *             or null if the file open filed.
+ *
+ * Use:                Opens a file so that it will be freed again when a pool is
+ *             destroyed.
+ */
+
+extern pool_file *pool_fopen(pool */*p*/,
+                            const char */*file*/, const char */*how*/);
+
+/* --- @pool_fclose@ --- *
+ *
+ * Arguments:  @pool_file *pf@ = pointer to a file resource
+ *
+ * Returns:    The response from the @fclose@ function.
+ *
+ * Use:                Closes a file.  It is not an error to close a file multiple
+ *             times.
+ */
+
+extern int pool_fclose(pool_file */*pf*/);
+
+/* --- @pool_subarena@ --- *
+ *
+ * Arguments:  @pool *p@ = pointer to the pool
+ *
+ * Returns:    A subarena built from the pool's memory allocator.
+ *
+ * Use:                Creates a suballocation arena attached to a pool.  The arena
+ *             and all of its memory will be freed when the pool is
+ *             destroyed.
+ */
+
+extern subarena *pool_subarena(pool */*p*/);
+
+/*----- That's all, folks -------------------------------------------------*/
+
+#ifdef __cplusplus
+  }
+#endif
+
+#endif
diff --git a/mem/sub.3 b/mem/sub.3
new file mode 100644 (file)
index 0000000..b1f6083
--- /dev/null
+++ b/mem/sub.3
@@ -0,0 +1,142 @@
+.\" -*-nroff-*-
+.de VS
+.sp 1
+.RS
+.nf
+.ft B
+..
+.de VE
+.ft R
+.fi
+.RE
+.sp 1
+..
+.TH sub 3 "8 May 1999" "Straylight/Edgeware" "mLib utilities library"
+.SH NAME
+sub \- efficient allocation and freeing of small blocks
+.\" @subarena_create
+.\" @subarena_destroy
+.\" @subarena_alloc
+.\" @subarena_free
+.\" @sub_alloc
+.\" @sub_free
+.\" @sub_init
+.\"
+.\" @A_CREATE
+.\" @A_DESTROY
+.\" @CREATE
+.\" @DESTROY
+.\"
+.SH SYNOPSIS
+.nf
+.B "#include <mLib/sub.h>"
+
+.BI "void subarena_create(subarena *" s ", arena *" a );
+.BI "void subarena_destroy(subarena *" s );
+.BI "void subarena_alloc(subarena *" s ", size_t " sz );
+.BI "void subarena_free(subarena *" s ", void *" p ", size_t " sz );
+
+.B "void sub_init(void);"
+.BI "void *sub_alloc(size_t " sz );
+.BI "void sub_free(void *" p ", size_t " sz );
+
+.BI "void *A_CREATE(subarena *" s ", " type );
+.BI "void A_DESTROY(subarena *" s ", " type " *" p );
+.BI "void *CREATE(" type );
+.BI "void DESTROY(" type " *" p );
+.fi
+.SH DESCRIPTION
+The
+.B subarena
+collection of functions and macros implement an efficient allocator for
+small blocks of known sizes, constructed from a general allocator
+implemented by an
+.BR arena (3).
+Free blocks of the same size are linked together in list, making freeing
+and allocation fast.  The `free' operation requires the block size as an
+argument, so there's no data overhead for an allocated block.  The
+system takes advantage of this by allocating big chunks from the
+underlying arena and splitting the chunks into smaller blocks of the
+right size, so the space and time overhead from the underlying allocator
+is divided over many blocks.
+.PP
+Calling
+.B subarena_alloc
+allocates a block of
+.I sz
+bytes from the subarena
+.IR s .
+If there isn't enough memory to allocate the block, the
+exception
+.B EXC_NOMEM
+is raised.
+.PP
+The
+.B subarena_free
+function frees a block allocated by
+.B subarena_alloc
+from the same subarena.  You must know the size of the block in advance.
+Note that
+.B subarena_free
+never gives memory back to the underlying allocator.  Free sub-blocks
+are just made available to later calls of
+.BR subarena_alloc .
+.PP
+Don't try to free blocks allocated by
+.B subarena_alloc
+to the underlying arena's
+.I free
+function, or to try freeing blocks obtained directly from the arena's
+.I alloc
+function using
+.BR subarena_free .
+If you do, you'll get what you deserve.
+.PP
+The pair of macros
+.B A_CREATE
+and
+.B A_DESTROY
+are intended to provide a slightly more natural interface to
+allocation.  The call
+.VS
+mystruct *p = subarena_alloc(s, sizeof(mystruct));
+.VE
+can be replaced by
+.VS
+mystruct p = A_CREATE(s, mystruct);
+.VE
+Similarly, the block can be freed by saying
+.VS
+A_DESTROY(s, p)
+.VE
+rather than the more cumbersome
+.VS
+subarena_free(s, p, sizeof(*p));
+.VE
+There is a standard subarena
+.B sub_global
+which uses
+.B arena_global
+as its underlying allocator (obtained the first time the subarena is
+used).  The functions
+.B sub_alloc
+and
+.B sub_free
+and the macros
+.B CREATE
+and
+.B DESTROY
+use this subarena.
+.PP
+The function
+.B sub_init
+ought to be called before any of the other functions as a matter of good
+taste, but actually the system will initialize itself the first time
+it's used.
+.SH "SEE ALSO"
+.BR arena (3),
+.BR exc (3),
+.BR alloc (3),
+.BR mLib (3).
+.SH AUTHOR
+Mark Wooding, <mdw@distorted.org.uk>
diff --git a/mem/sub.c b/mem/sub.c
new file mode 100644 (file)
index 0000000..1abfedd
--- /dev/null
+++ b/mem/sub.c
@@ -0,0 +1,372 @@
+/* -*-c-*-
+ *
+ * Allocation of known-size blocks
+ *
+ * (c) 1998 Straylight/Edgeware
+ */
+
+/*----- Licensing notice --------------------------------------------------*
+ *
+ * This file is part of the mLib utilities library.
+ *
+ * mLib is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * mLib is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with mLib; if not, write to the Free
+ * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+ * MA 02111-1307, USA.
+ */
+
+/*----- The big idea ------------------------------------------------------*
+ *
+ * This file provides an extra layer over @malloc@.  It provides fast
+ * turnover for small blocks, and tries to minimize the per-block overhead.
+ *
+ * To do its job, @alloc@ must place an extra restriction on you: you must
+ * know the size of a block when you free it.  Usually you'll have this
+ * information encoded in some way either in the block or in the thing that
+ * referenced it, so this isn't a hardship.
+ *
+ * It works fairly simply.  If a request for a big block (as defined by the
+ * constants below) comes in, it gets sent on to @malloc@ unmolested.  For
+ * small blocks, it goes straight to a `bin' -- a list containing free blocks
+ * of exactly that size, or the nearest bigger size we can manage.  If the
+ * bin is empty, a `chunk' is allocated from @malloc@: this has enough room
+ * for lots of blocks of the requested size, so it ets split up and each
+ * individual small block is added to the bin list.  The first block in the
+ * bin list is then removed and given to the caller.  In this way, @malloc@
+ * only stores its information once for lots of little blocks, so we save
+ * memory.  Because I know where the correct bin is just from the block size,
+ * and I don't need to do any searching at all in the usual case (because the
+ * list isn't empty) I can get a speed advantage too.
+ *
+ * This code is almost certainly not ANSI conformant, although I'm not
+ * actually sure.  If some kind soul would let me know how seriously I've
+ * violated the standard, and whether this is easily fixable, I'd be
+ * grateful.
+ */
+
+/*----- Header files ------------------------------------------------------*/
+
+/* --- ANSI headers --- */
+
+#include <assert.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+/* --- Local headers --- */
+
+#include "arena.h"
+#include "exc.h"
+#include "sub.h"
+
+/*----- Configuration tweaks ----------------------------------------------*/
+
+/* #define SUBARENA_TRIVIAL */
+
+/*----- Static variables --------------------------------------------------*/
+
+static size_t sizes[SUB_BINS];
+
+/*----- Global variables --------------------------------------------------*/
+
+subarena sub_global;
+
+#ifdef SUBARENA_TRIVIAL
+
+typedef struct sub_link {
+  struct sub_link *next;
+  void *p;
+  size_t sz;
+} sub_link;
+
+#endif
+
+/*----- Main code ---------------------------------------------------------*/
+
+/* --- @subarena_create@ --- *
+ *
+ * Arguments:  @subarena *s@ = pointer to arena to initialize
+ *             @arena *a@ = pointer to underlying arena block
+ *
+ * Returns:    ---
+ *
+ * Use:                Initialize a suballocation arena based on an underlying large
+ *             blocks arena.
+ */
+
+void subarena_create(subarena *s, arena *a)
+{
+#ifdef SUBARENA_TRIVIAL
+  s->bin[0] = 0;
+#else
+  size_t i;
+  if (!sizes[1])
+    sub_init();
+  for (i = 0; i < SUB_BINS; i++)
+    s->bin[i] = 0;
+#endif
+  s->a = a;
+}
+
+/* --- @subarena_destroy@ --- *
+ *
+ * Arguments:  @subarena *s@ = pointer to arena to destroy
+ *
+ * Returns:    ---
+ *
+ * Use:                Destroys a suballocation arena, freeing all of the memory it
+ *             contains back to the underlying large blocks arena.
+ */
+
+void subarena_destroy(subarena *s)
+{
+#ifdef SUBARENA_TRIVIAL
+
+  sub_link *l, *ll;
+
+  for (l = s->bin[0]; l; l = ll) {
+    ll = l;
+    a_free(s->a, l->p);
+    a_free(s->a, l);
+  }
+  s->bin[0] = 0;
+
+#else
+
+  size_t i;
+  for (i = 0; i < SUB_BINS; i++) {
+    void *p = s->bin[i];
+    while (p) {
+      void *q = p;
+      p = *(void **)q;
+      A_FREE(s->a, q);
+    }
+    s->bin[i] = 0;
+  }
+
+#endif
+}
+
+/* --- @subarena_alloc@ --- *
+ *
+ * Arguments:  @subarena *s@ = pointer to arena
+ *             @size_t s@ = size of chunk wanted
+ *
+ * Returns:    Pointer to a block at least as large as the one wanted.
+ *
+ * Use:                Allocates a small block of memory from the given pool.  The
+ *             exception @EXC_NOMEM@ is raised if the underlying arena is
+ *             full.
+ */
+
+void *subarena_alloc(subarena *s, size_t sz)
+{
+#ifdef SUBARENA_TRIVIAL
+
+  sub_link *l;
+  void *p;
+
+  if (!s->a)
+    subarena_create(s, arena_global);
+
+  if ((l = a_alloc(s->a, sizeof(*l))) == 0)
+    return (0);
+  if ((p = a_alloc(s->a, sz)) == 0) {
+    a_free(s->a, l);
+    return (0);
+  }
+  l->p = p;
+  l->sz = sz;
+  l->next = s->bin[0];
+  s->bin[0] = l;
+  return (p);
+
+#else
+
+  int bin;
+  void *p;
+
+  /* --- Ensure that everything is initialized --- */
+
+  if (!s->a)
+    subarena_create(s, arena_global);
+
+  /* --- Handle oversize blocks --- */
+
+  bin = SUB_BIN(sz);
+  if (bin >= SUB_BINS) {
+    void *p = A_ALLOC(s->a, sz);
+    if (!p)
+      THROW(EXC_NOMEM);
+    return (p);
+  }
+
+  /* --- If the bin is empty, find some memory --- */
+
+  if (!s->bin[bin]) {
+    char *p, *q;
+
+    p = A_ALLOC(s->a, sizes[bin]);
+    if (!p)
+      THROW(EXC_NOMEM);
+    q = p + sizes[bin];
+
+    sz = SUB_BINSZ(bin);
+
+    q -= sz;
+    *(void **)q = 0;
+
+    while (q > p) {
+      q -= sz;
+      *(void **)q = q + sz;
+    }
+
+    s->bin[bin] = p;
+  }
+
+  /* --- Extract the first block in the list --- */
+
+  p = s->bin[bin];
+  s->bin[bin] = *(void **)p;
+  return (p);
+
+#endif
+}
+
+/* --- @subarena_free@ --- *
+ *
+ * Arguments:  @subarena *s@ = pointer to arena
+ *             @void *p@ = address of block to free
+ *             @size_t s@ = size of block
+ *
+ * Returns:    ---
+ *
+ * Use:                Frees a block allocated by @subarena_alloc@.
+ */
+
+void subarena_free(subarena *s, void *p, size_t sz)
+{
+#ifdef SUBARENA_TRIVIAL
+
+  sub_link *lh = s->bin[0], **l, *ll;
+
+  for (l = &lh; *l && (*l)->p != p; l = &(*l)->next)
+    ;
+  ll = *l;
+  assert(ll);
+  assert(ll->sz == sz);
+  *l = ll->next;
+  a_free(s->a, ll);
+  a_free(s->a, p);
+  s->bin[0] = lh;
+
+#else
+
+  int bin = SUB_BIN(sz);
+
+  if (bin >= SUB_BINS)
+    A_FREE(s->a, p);
+  else {
+    *(void **)p = s->bin[bin];
+    s->bin[bin] = p;
+  }
+
+#endif
+}
+
+/*----- Compatibility stuff -----------------------------------------------*/
+
+/* --- @sub_alloc@ --- *
+ *
+ * Arguments:  @size_t s@ = size of chunk wanted
+ *
+ * Returns:    Pointer to a block at least as large as the one wanted.
+ *
+ * Use:                Allocates a small block of memory from the @sub_global@ pool.
+ */
+
+void *(sub_alloc)(size_t sz) { return sub_alloc(sz); }
+
+/* --- @sub_free@ --- *
+ *
+ * Arguments:  @void *p@ = address of block to free
+ *             @size_t s@ = size of block
+ *
+ * Returns:    ---
+ *
+ * Use:                Frees a block allocated by @sub_alloc@.
+ */
+
+void (sub_free)(void *p, size_t sz) { sub_free(p, sz); }
+
+/* --- @sub_init@ --- *
+ *
+ * Arguments:  ---
+ *
+ * Returns:    ---
+ *
+ * Use:                Initializes the magic allocator.
+ */
+
+void sub_init(void)
+{
+#ifndef SUBARENA_TRIVIAL
+  int i;
+
+  /* --- Initialize the sizes bins --- */
+
+  for (i = 1; i < SUB_BINS; i++) {
+    sizes[i] = ((SUB_CHUNK + SUB_BINSZ(i) - 1) /
+                    SUB_BINSZ(i) * SUB_BINSZ(i));
+  }
+#endif
+}
+
+/*----- Debugging code ----------------------------------------------------*/
+
+#ifdef TEST_RIG
+
+#define BLOCKS 1024
+#define SIZE_MAX 2048
+#define ITERATIONS 500000
+
+int main(void)
+{
+  static void *block[BLOCKS];
+  static size_t size[BLOCKS];
+  size_t allocced = 0;
+  int i;
+  long count;
+
+  sub_init();
+
+  for (count = 0; count < ITERATIONS; count++) {
+    i = rand() % BLOCKS;
+    if (block[i]) {
+      sub_free(block[i], size[i]);
+      block[i] = 0;
+      allocced -= size[i];
+    } else {
+      block[i] = sub_alloc(size[i] =
+                          rand() % (SUB_MAXBIN - 128) + 128);
+      allocced += size[i];
+      memset(block[i], 0, size[i]);    /* trample allocated storage */
+    }
+  }
+
+  return (0);
+}
+
+#endif
+
+/*----- That's all, folks -------------------------------------------------*/
diff --git a/mem/sub.h b/mem/sub.h
new file mode 100644 (file)
index 0000000..b48e835
--- /dev/null
+++ b/mem/sub.h
@@ -0,0 +1,252 @@
+/* -*-c-*-
+ *
+ * Allocation of known-size blocks
+ *
+ * (c) 1998 Straylight/Edgeware
+ */
+
+/*----- Licensing notice --------------------------------------------------*
+ *
+ * This file is part of the mLib utilities library.
+ *
+ * mLib is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * mLib is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with mLib; if not, write to the Free
+ * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+ * MA 02111-1307, USA.
+ */
+
+#ifndef MLIB_SUB_H
+#define MLIB_SUB_H
+
+#ifdef __cplusplus
+  extern "C" {
+#endif
+
+/*----- Required header files ---------------------------------------------*/
+
+#include <stdlib.h>
+
+#ifndef MLIB_ALIGN_H
+#  include "align.h"
+#endif
+
+#ifndef MLIB_ARENA_H
+#  include "arena.h"
+#endif
+
+/*----- Configuration and tuning ------------------------------------------*/
+
+/* --- The largest block I'll handle here --- *
+ *
+ * Anything larger will be handed on to the underlying @alloc@.
+ */
+
+#define SUB_MAXBIN 256
+
+/* --- Preferred chunk size --- *
+ *
+ * When a bin is empty, I'll allocate a large chunk of approximately this
+ * size and divvy it up into small bin-sized blocks.
+ */
+
+#define SUB_CHUNK 4096
+
+/*----- Other useful macros -----------------------------------------------*/
+
+/* --- The granularity of bin buffers --- *
+ *
+ * All blocks allocated by the binner are a multiple of this size.
+ */
+
+#define SUB_GRANULE sizeof(union align)
+
+/* --- Finding the right bin for a given size --- *
+ *
+ * This chooses the correct bin for an allocation.  Input is the size of
+ * block wanted; result is the bin index.
+ */
+
+#define SUB_BIN(x) (((x) + SUB_GRANULE - 1) / SUB_GRANULE)
+
+/* --- Convert a bin back to the block size --- *
+ *
+ * This gives the size of block contained in a given bin.
+ */
+
+#define SUB_BINSZ(x) ((x) * SUB_GRANULE)
+
+/* --- Number of bins required --- */
+
+#define SUB_BINS (SUB_MAXBIN / SUB_GRANULE + 1)
+
+/*----- Data structures ---------------------------------------------------*/
+
+typedef struct subarena {
+  arena *a;
+  void *bin[SUB_BINS];
+} subarena;
+
+/*----- Global variables --------------------------------------------------*/
+
+extern subarena sub_global;
+
+/*----- Functions provided ------------------------------------------------*/
+
+/* --- @subarena_create@ --- *
+ *
+ * Arguments:  @subarena *s@ = pointer to arena to initialize
+ *             @arena *a@ = pointer to underlying arena block
+ *
+ * Returns:    ---
+ *
+ * Use:                Initialize a suballocation arena based on an underlying large
+ *             blocks arena.
+ */
+
+extern void subarena_create(subarena */*s*/, arena */*a*/);
+
+/* --- @subarena_destroy@ --- *
+ *
+ * Arguments:  @subarena *s@ = pointer to arena to destroy
+ *
+ * Returns:    ---
+ *
+ * Use:                Destroys a suballocation arena, freeing all of the memory it
+ *             contains back to the underlying large blocks arena.
+ */
+
+extern void subarena_destroy(subarena */*s*/);
+
+/* --- @subarena_alloc@ --- *
+ *
+ * Arguments:  @subarena *s@ = pointer to arena
+ *             @size_t s@ = size of chunk wanted
+ *
+ * Returns:    Pointer to a block at least as large as the one wanted.
+ *
+ * Use:                Allocates a small block of memory from the given pool.  The
+ *             exception @EXC_NOMEM@ is raised if the underlying arena is
+ *             full.
+ */
+
+extern void *subarena_alloc(subarena */*s*/, size_t /*sz*/);
+
+/* --- @subarena_free@ --- *
+ *
+ * Arguments:  @subarena *s@ = pointer to arena
+ *             @void *p@ = address of block to free
+ *             @size_t s@ = size of block
+ *
+ * Returns:    ---
+ *
+ * Use:                Frees a block allocated by @subarena_alloc@.
+ */
+
+extern void subarena_free(subarena */*s*/, void */*p*/, size_t /*sz*/);
+
+/* --- @A_CREATE@ --- *
+ *
+ * Arguments:  @subarena *s@ = pointer to arena
+ *             @type@ = type of object required; must be passable to
+ *                     @sizeof@
+ *
+ * Returns:    Pointer to a block sufficiently big to hold an object of the
+ *             named type.
+ *
+ * Use:                Allocates a block of the required type.
+ */
+
+#define A_CREATE(a, type) subarena_alloc((a), sizeof(type))
+
+/* --- @A_DESTROY@ --- *
+ *
+ * Arguments:  @subarena *s@ = pointer to arena
+ *             @void *p@ = pointer to an object
+ *
+ * Returns:    ---
+ *
+ * Use:                Frees the thing pointed to by @p@.
+ */
+
+#define A_DESTROY(a, p) subarena_free((a), (p), sizeof(*p))
+
+/*----- Shortcuts for the global pool -------------------------------------*/
+
+/* --- @sub_alloc@ --- *
+ *
+ * Arguments:  @size_t s@ = size of chunk wanted
+ *
+ * Returns:    Pointer to a block at least as large as the one wanted.
+ *
+ * Use:                Allocates a small block of memory from the @sub_global@ pool.
+ */
+
+extern void *sub_alloc(size_t /*sz*/);
+#define sub_alloc(sz) subarena_alloc(&sub_global, (sz))
+
+/* --- @sub_free@ --- *
+ *
+ * Arguments:  @void *p@ = address of block to free
+ *             @size_t s@ = size of block
+ *
+ * Returns:    ---
+ *
+ * Use:                Frees a block allocated by @sub_alloc@.
+ */
+
+extern void sub_free(void */*p*/, size_t /*sz*/);
+#define sub_free(p, sz) subarena_free(&sub_global, (p), (sz))
+
+/* --- @CREATE@ --- *
+ *
+ * Arguments:  @type@ = type of object required; must be passable to
+ *                     @sizeof@
+ *
+ * Returns:    Pointer to a block sufficiently big to hold an object of the
+ *             named type.
+ *
+ * Use:                Allocates a block of the required type.
+ */
+
+#define CREATE(type) sub_alloc(sizeof(type))
+
+/* --- @DESTROY@ --- *
+ *
+ * Arguments:  @void *p@ = pointer to an object
+ *
+ * Returns:    ---
+ *
+ * Use:                Frees the thing pointed to by @p@.
+ */
+
+#define DESTROY(p) sub_free(p, sizeof(*p))
+
+/* --- @sub_init@ --- *
+ *
+ * Arguments:  ---
+ *
+ * Returns:    ---
+ *
+ * Use:                Initializes the magic allocator.  This is no longer
+ *             necessary.
+ */
+
+extern void sub_init(void);
+
+/*----- That's all, folks -------------------------------------------------*/
+
+#ifdef __cplusplus
+  }
+#endif
+
+#endif
diff --git a/sel/Makefile.am b/sel/Makefile.am
new file mode 100644 (file)
index 0000000..2e87e7d
--- /dev/null
@@ -0,0 +1,74 @@
+### -*-makefile-*-
+###
+### Build script for event-driven networking
+###
+### (c) 2009 Straylight/Edgeware
+###
+
+###----- Licensing notice ---------------------------------------------------
+###
+### This file is part of the mLib utilities library.
+###
+### mLib is free software; you can redistribute it and/or modify
+### it under the terms of the GNU Library General Public License as
+### published by the Free Software Foundation; either version 2 of the
+### License, or (at your option) any later version.
+###
+### mLib is distributed in the hope that it will be useful,
+### but WITHOUT ANY WARRANTY; without even the implied warranty of
+### MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+### GNU Library General Public License for more details.
+###
+### You should have received a copy of the GNU Library General Public
+### License along with mLib; if not, write to the Free
+### Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+### MA 02111-1307, USA.
+
+include $(top_srcdir)/vars.am
+
+noinst_LTLIBRARIES      = libsel.la
+libsel_la_SOURCES       =
+
+###--------------------------------------------------------------------------
+### Component files.
+
+## Core event selection.
+pkginclude_HEADERS     += sel.h
+libsel_la_SOURCES      += sel.c
+LIBMANS                        += sel.3
+
+## Waiting for buffers to fill.
+pkginclude_HEADERS     += selbuf.h selpk.h
+libsel_la_SOURCES      += selbuf.c selpk.c
+LIBMANS                        += selbuf.3 selpk.3
+
+## RFC931 identification.
+pkginclude_HEADERS     += ident.h
+libsel_la_SOURCES      += ident.c
+LIBMANS                        += ident.3
+
+## Nonblocking connections.
+pkginclude_HEADERS     += conn.h
+libsel_la_SOURCES      += conn.c
+LIBMANS                        += conn.3
+
+## Signal handling
+pkginclude_HEADERS     += sig.h
+libsel_la_SOURCES      += sig.c
+LIBMANS                        += sig.3
+
+## Name resolution.
+pkginclude_HEADERS     += bres.h
+LIBMANS                        += bres.3
+
+if WITH_ADNS
+libsel_la_SOURCES      += bres-adns.c
+else
+libsel_la_SOURCES      += bres.c
+pkglibexec_PROGRAMS     = bres
+bres_SOURCES            = bres.c
+bres_CPPFLAGS           = -DBRES_STANDALONE $(AM_CPPFLAGS)
+bres_LDADD              =
+endif
+
+###----- That's all, folks --------------------------------------------------
diff --git a/sel/bres-adns.c b/sel/bres-adns.c
new file mode 100644 (file)
index 0000000..491a9a8
--- /dev/null
@@ -0,0 +1,332 @@
+/* -*-c-*-
+ *
+ * Background reverse name resolution (ADNS version)
+ *
+ * (c) 2003 Straylight/Edgeware
+ */
+
+/*----- Licensing notice --------------------------------------------------*
+ *
+ * This file is part of the mLib utilities library.
+ *
+ * mLib is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * mLib is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with mLib; if not, write to the Free
+ * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+ * MA 02111-1307, USA.
+ *
+ *
+ * HOWEVER, since GNU adns is covered by the full GNU General Public
+ * License, this file (only) is also covered by the full GNU GPL, and
+ * you may NOT take advantage of the more liberal conditions of the
+ * LGPL when modifying or redistributing this file.  This doesn't mean
+ * that a program which uses the interface provided by this file is
+ * covered by the GPL, since @bres.c@ provides the same interface and
+ * is LGPL.  However, it does mean that if your program depends, for
+ * some reason (e.g., to meet particular performance criteria), on
+ * this adns-based implementation of mLib's background resolver then it
+ * must be licensed under the full GPL.
+ */
+
+#include "config.h"
+
+#ifndef HAVE_ADNS
+#  error "You need the ADNS library to compile this file."
+#endif
+
+/*----- Header files ------------------------------------------------------*/
+
+#include <assert.h>
+#include <errno.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <sys/types.h>
+#include <sys/time.h>
+#include <unistd.h>
+#include <sys/wait.h>
+
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+
+#include <adns.h>
+
+#include "alloc.h"
+#include "bres.h"
+#include "macros.h"
+#include "report.h"
+#include "sel.h"
+
+/*----- Static variables --------------------------------------------------*/
+
+static adns_state ads;
+static sel_state *sel;
+static sel_hook selhook;
+
+/*----- Main code ---------------------------------------------------------*/
+
+/* --- @bres_abort@ --- *
+ *
+ * Arguments:  @bres_client *rc@ = pointer to client block
+ *
+ * Returns:    ---
+ *
+ * Use:                Removes a queued job.
+ */
+
+void bres_abort(bres_client *rc)
+{
+  if (rc->q == adns_r_addr) xfree(rc->u.name);
+  if (rc->a) free(rc->a);
+  adns_cancel(rc->aq);
+}
+
+/* --- @bres_byaddr@ --- *
+ *
+ * Arguments:  @bres_client *rc@ = pointer to client block
+ *             @struct in_addr addr@ = address to resolve
+ *             @void (*func)(struct hostent *h, void *p)@ = handler function
+ *             @void *p@ = argument for handler function
+ *
+ * Returns:    ---
+ *
+ * Use:                Adds an address lookup job to the queue.  The job will be
+ *             processed when there's a spare resolver process to deal with
+ *             it.
+ */
+
+void bres_byaddr(bres_client *rc, struct in_addr addr,
+                void (*func)(struct hostent */*h*/, void */*p*/),
+                void *p)
+{
+  int e;
+  struct sockaddr_in sin;
+
+  if (!ads) goto fail;
+  sin.sin_family = AF_INET;
+  sin.sin_addr = addr;
+  sin.sin_port = 0;
+  if ((e = adns_submit_reverse(ads, (struct sockaddr *)&sin, adns_r_ptr,
+                              0, rc, &rc->aq)) != 0)
+    goto fail;
+  rc->a = 0;
+  rc->q = adns_r_ptr;
+  rc->u.addr = addr;
+  rc->func = func;
+  rc->p = p;
+  return;
+
+fail:
+  func(0, p);
+}
+
+/* --- @bres_byname@ --- *
+ *
+ * Arguments:  @bres_client *rc@ = pointer to client block
+ *             @const char *name@ = name to resolve
+ *             @void (*func)(struct hostent *h, void *p)@ = handler function
+ *             @void *p@ = argument for handler function
+ *
+ * Returns:    ---
+ *
+ * Use:                Adds a name lookup job to the queue.  The job will be
+ *             processed when there's a spare resolver process to deal with
+ *             it.
+ */
+
+void bres_byname(bres_client *rc, const char *name,
+                void (*func)(struct hostent */*h*/, void */*p*/),
+                void *p)
+{
+  int e;
+
+  if (!ads) goto fail;
+  if ((e = adns_submit(ads, name, adns_r_addr,
+                      adns_qf_search | adns_qf_owner, rc, &rc->aq)) != 0)
+    goto fail;
+  rc->a = 0;
+  rc->q = adns_r_addr;
+  rc->u.name = xstrdup(name);
+  rc->func = func;
+  rc->p = p;
+  return;
+
+fail:
+  func(0, p);
+}
+
+/* --- @bres_exec@ --- *
+ *
+ * Arguments:  @const char *file@ = file containing server code or null
+ *
+ * Returns:    ---
+ *
+ * Use:                Makes `bres' use a standalone server rather than copies of
+ *             the current process.  This can reduce memory consumption for
+ *             large processes, at the expense of startup time (which
+ *             shouldn't be too bad anyway, because of the resolver design).
+ *             If the filename is null, a default set up at install time is
+ *             used.  It's probably a good idea to leave it alone.
+ */
+
+void bres_exec(const char *file)
+{
+  /* Nothin' doin' */
+}
+
+/* --- @report@ --- *
+ *
+ * Arguments:  @bres_client *c@ = client descriptor block
+ *             @adns_answer *a@ = A-record answer from resolver library
+ *             @adns_rr_addr *av@ = vector of address records
+ *             @int an@ = number of address records (must be positive)
+ *             @char **nv@ = vector of name strings
+ *             @int nn@ = number of name strings (must be positive)
+ *
+ * Returns:    ---
+ *
+ * Use:                Formats the given answer into a @struct hostent@ and reports
+ *             it to the waiting client application.
+ */
+
+static void report(bres_client *rc, adns_answer *a,
+                  adns_rr_addr *av, int an,
+                  char **nv, int nn)
+{
+  struct hostent h;
+  char *n[16];
+  char *aa[16];
+  int i, j;
+
+  j = 0;
+  if (a->cname) n[j++] = a->cname;
+  else { n[j++] = *nv; nv++; nn--; }
+  for (i = 0; i < nn && j < N(n) - 1; i++)
+    if (STRCMP(n[0], !=, nv[i])) n[j++] = nv[i];
+  n[j++] = 0;
+  for (i = j = 0; i < an && j < N(aa) - 1; i++) {
+    if (av[i].addr.sa.sa_family == AF_INET)
+      aa[j++] = (char *)&av[i].addr.inet.sin_addr;
+  }
+  aa[j++] = 0;
+  h.h_name = n[0];
+  h.h_aliases = n + 1;
+  h.h_addrtype = AF_INET;
+  h.h_length = sizeof(struct in_addr);
+  h.h_addr_list = aa;
+  rc->func(&h, rc->p);
+}
+
+/* --- @beforehook@, @afterhook@ --- *
+ *
+ * Arguments:  @sel_state *s@ = select state
+ *             @sel_args *sa@ = argument block
+ *             @void *p@ = uninteresting pointer
+ *
+ * Returns:    ---
+ *
+ * Use:                Processes the selector's arguments before @select@ is
+ *             called, to allow ADNS to do its thing.
+ */
+
+static void beforehook(sel_state *s, sel_args *sa, void *p)
+{
+  adns_beforeselect(ads, &sa->maxfd,
+                   &sa->fd[SEL_READ], &sa->fd[SEL_WRITE], &sa->fd[SEL_EXC],
+                   &sa->tvp, &sa->tv, &sa->now);
+}
+
+static void afterhook(sel_state *s, sel_args *sa, void *p)
+{
+  void *c;
+  bres_client *rc;
+  adns_query q;
+  adns_answer *a, *aa;
+  int e;
+  int i;
+
+  adns_afterselect(ads, sa->maxfd,
+                  &sa->fd[SEL_READ], &sa->fd[SEL_WRITE], &sa->fd[SEL_EXC],
+                  &sa->now);
+  while (q = 0, (e = adns_check(ads, &q, &a, &c)) == 0) {
+    rc = c;
+    if (a->status != 0)
+      goto fail;
+    else switch (rc->q) {
+      case adns_r_addr:
+       assert(a->type == adns_r_addr);
+       xfree(rc->u.name);
+       report(rc, a, a->rrs.addr, a->nrrs, &a->owner, 1);
+       free(a);
+       break;
+      case adns_r_ptr:
+       if (a->type == adns_r_ptr) {
+         rc->a = a;
+         if ((e = adns_submit(ads, a->rrs.str[0], adns_r_addr,
+                              0, rc, &q)) != 0)
+           goto fail;
+         rc->aq = q;
+       } else {
+         assert(a->type == adns_r_addr);
+         for (i = 0; i < a->nrrs; i++) {
+           if (a->rrs.addr[i].addr.sa.sa_family == AF_INET &&
+               a->rrs.addr[i].addr.inet.sin_addr.s_addr ==
+                 rc->u.addr.s_addr)
+             goto match;
+         }
+         goto fail;
+       match:
+         aa = rc->a;
+         report(rc, a, &a->rrs.addr[i], 1, aa->rrs.str, aa->nrrs);
+         free(aa);
+         free(a);
+       }
+       break;
+      default:
+       abort();
+    }
+    continue;
+
+  fail:
+    if (rc->q == adns_r_addr) xfree(rc->u.name);
+    if (rc->a) free(rc->a);
+    rc->func(0, rc->p);
+    free(a);
+  }
+}
+
+/* --- @bres_init@ --- *
+ *
+ * Arguments:  @sel_state *s@ = pointer to select multiplexor
+ *
+ * Returns:    ---
+ *
+ * Use:                Initializes the background resolver for use.
+ */
+
+void bres_init(sel_state *s)
+{
+  int e;
+
+  if ((e = adns_init(&ads, adns_if_noautosys, 0)) != 0) {
+    moan("adns_init failed: resolver won't work");
+    return;
+  }
+  sel_addhook(s, &selhook, beforehook, afterhook, 0);
+  sel = s;
+}
+
+/*----- That's all, folks -------------------------------------------------*/
diff --git a/sel/bres.3 b/sel/bres.3
new file mode 100644 (file)
index 0000000..ca58165
--- /dev/null
@@ -0,0 +1,131 @@
+.\" -*-nroff-*-
+.TH bres 3 "1 October 1999" "Straylight/Edgeware" "mLib utilities library"
+.SH NAME
+bres \- background name resolver
+.\" @bres_abort
+.\" @bres_byname
+.\" @bres_byaddr
+.\" @bres_exec
+.\" @bres_init
+.SH SYNOPSIS
+.nf
+.B "#include <mLib/bres.h>"
+
+.BI "void bres_byname(bres_client *" rc ", const char *" name ,
+.BI "                 void (*" func ")(struct hostent *" h ", void *" p ),
+.BI "                 void *" p );
+.BI "void bres_byaddr(bres_client *" rc ", struct inaddr " addr ,
+.BI "                 void (*" func ")(struct hostent *" h ", void *" p ),
+.BI "                 void *" p );
+.BI "void bres_abort(bres_client *" rc );
+.BI "void bres_exec(const char *" file );
+.BI "void bres_init(sel_state *" sel );
+.fi
+.SH DESCRIPTION
+The
+.B bres.h
+header file declares types and functions for doing translation between
+host names and IP addresses in the background.
+.PP
+The system must be initialized before use by a call to
+.BR bres_init ,
+passing it the address of an I/O multiplexor (see
+.BR sel (3)).
+.PP
+A resolver task is stored in an object of type
+.BR bres_client ,
+the storage for which is allocated by the caller.  The object is a
+structure, and its contents are unspecified.  The object is initialized
+by one of the name resolution functions
+.B bres_byname
+and
+.BR bres_byaddr .
+Each function is passed the following arguments:
+.TP
+.BI "bres_client *" rc
+Pointer to the client block to initialize and store the resolver job's
+state.
+.TP
+.BI "struct in_addr " addr "\fR (\fBbres_byaddr\fR)"
+.sp -1
+.TP
+.BI "const char *" name "\fR (\fBbres_byname\fR)"
+The IP address or hostname to resolve.
+.TP
+.BI "void (*" func ")(struct hostent *" h ", void *" p )
+A handler function to call when the resolver job is complete.
+.TP
+.BI "void *" p
+A pointer argument to pass to the handler function.
+.PP
+The
+.B bres_client
+block must not be discarded until either the job is complete (i.e., the
+handler function has been called) or
+.B bres_abort
+is called on it.
+.PP
+The handler function is passed either the address of a
+.B "struct hostent"
+structure describing the resolved host, or a null pointer indicating
+failure.  The
+.B hostent
+structure is as returned by the standard
+.BR gethostbyname (3)
+and
+.BR gethostbyaddr (3)
+functions.  This isn't the most convenient format for the results, but
+it does have the benefit of being standard.  Similarly, errors are
+reported through the global
+.B h_errno
+variable.
+.PP
+The function
+.B bres_abort
+cancels a running resolver job.  When it returns, the client structure
+is safe to discard.
+.PP
+There are two versions of
+.BR bres .
+The standard one uses a pool of server processes.  Incoming resolver
+jobs are passed to an available server, or a new server is started if
+all are busy.  There is a maximum number of servers, and jobs are queued
+once this limit is reached.  Old servers which have been idle for a
+period of time are killed off.  Servers are also killed if they start
+misbehaving or their jobs are aborted.
+.PP
+By default, servers are started simply by calling
+.BR fork (2).
+This can cause undesirably high memory usage in large programs.  The
+function
+.B bres_exec
+requests the resolver system to
+.BR exec (2)
+a small dedicated server program to perform name lookups to reduce
+memory consumption.  The argument to
+.B bres_exec
+is the full pathname of the server program, or null to accept the
+default set at library configuration time (which is usually correct).
+.PP
+The other implementation of
+.B bres
+uses the
+.B adns
+library to do asynchronous resolution.  It can cope with many more
+simultaneous resolver jobs, and doesn't use up external processes.  If
+you're using the
+.BR adns -based
+resolver, then the
+.B bres_exec
+function does nothing at all.
+.PP
+For security reasons, when an address is resolved, the hostname received
+is verified by performing a forward lookup.  If the forward lookup fails
+to return the expected IP address, an error is reported.
+.SH "SEE ALSO"
+.BR gethostbyname (3),
+.BR gethostbyaddr (3),
+.BR sel (3),
+.BR mLib (3).
+.SH "AUTHOR"
+Mark Wooding, <mdw@distorted.org.uk>
diff --git a/sel/bres.c b/sel/bres.c
new file mode 100644 (file)
index 0000000..22da560
--- /dev/null
@@ -0,0 +1,973 @@
+/* -*-c-*-
+ *
+ * Background reverse name resolution
+ *
+ * (c) 1999 Straylight/Edgeware
+ */
+
+/*----- Licensing notice --------------------------------------------------*
+ *
+ * This file is part of the mLib utilities library.
+ *
+ * mLib is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * mLib is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with mLib; if not, write to the Free
+ * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+ * MA 02111-1307, USA.
+ */
+
+/*----- Header files ------------------------------------------------------*/
+
+#include "config.h"
+
+#include <errno.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <sys/types.h>
+#include <sys/time.h>
+#include <unistd.h>
+#include <sys/wait.h>
+
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+
+#include "alloc.h"
+#include "bres.h"
+#include "mdup.h"
+#include "report.h"
+#include "sel.h"
+
+/*----- Magic numbers -----------------------------------------------------*/
+
+#define BRES_MAX 15                    /* Maximum number of resolvers */
+#define BRES_IDLE 60                   /* Lifetime of an idle resolver */
+
+/*----- Static variables --------------------------------------------------*/
+
+#ifndef BRES_STANDALONE
+
+static bres_server servers[BRES_MAX];  /* Statically allocated servers */
+
+static bres_server *freelist, *freetail;
+static bres_client *qhead, *qtail;
+static sel_state *sel;
+static const char *server = 0;
+
+#define UNLINK(head, tail, p) do {                                     \
+  *((p)->next ? &(p)->next->prev : &(tail)) = (p)->prev;               \
+  *((p)->prev ? &(p)->prev->next : &(head)) = (p)->next;               \
+} while (0)
+
+#define LINKHEAD(head, tail, p) do {                                   \
+  (p)->next = (head);                                                  \
+  (p)->prev = 0;                                                       \
+  *((head) ? &(head)->prev : &(tail)) = (p);                           \
+  (head) = (p);                                                                \
+} while (0)
+
+#define LINKTAIL(head, tail, p) do {                                   \
+  (p)->next = 0;                                                       \
+  (p)->prev = (head);                                                  \
+  *((tail) ? &(tail)->next : &(head)) = (p);                           \
+  (tail) = (p);                                                                \
+} while (0)
+
+#endif
+
+/*----- Background resolver protocol --------------------------------------*/
+
+/* --- Requests and responses --- *
+ *
+ * There are two types of requests: name and addr, corresponding to the
+ * standard @gethostbyname@ and @gethostbyaddr@ calls.  There are two types
+ * of responses too: a positive response consists of an encoded equivalent of
+ * a @struct hostent@ structure containing the requested information; a
+ * negative response consists of an @h_errno@ value explaining the problem.
+ */
+
+#define BRES_BYNAME 0                  /* Request: resolve given name */
+#define BRES_BYADDR 1                  /* Request: resolve given address */
+
+#define BRES_HOSTENT 0                 /* Response: resolved ok */
+#define BRES_ERROR 1                   /* Response: resolution failed */
+
+/* --- Encodings --- *
+ *
+ * A string is encoded as a @size_t@ length followed by the actual data.  The
+ * null terminator is not transmitted.
+ *
+ * Addresses for resolution are transmitted as raw @struct in_addr@
+ * structures.
+ *
+ * A @hostent@ structure is transmitted as a header containing fixed-size
+ * information, followed by the official name, an array of aliases, and an
+ * array of addresses.  The number of items in the arrays is specified in the
+ * header.
+ *
+ * The implementation assumes that a complete request or reply is always
+ * sent.  Undesirable blocking will occur if this is not the case.  Both ends
+ * are assumed to trust each other.  A protocol failure results in the child
+ * in question being terminated.
+ */
+
+typedef struct hostskel {
+  size_t nalias;
+  int addrtype;
+  size_t addrsz;
+  size_t naddr;
+} hostskel;
+
+/* --- @doread@, @dowrite@ --- *
+ *
+ * Arguments:  @int fd@ = file descriptor
+ *             @void *buf@ = buffer for data
+ *             @size_t sz@ = size of data
+ *
+ * Returns:    Zero if successful, nonzero otherwise.
+ *
+ * Use:                Reads or writes a chunk of data.  @EINTR@ errors are retried;
+ *             incomplete reads and writes are continued from where they
+ *             left off.  End-of-file is considered an I/O error.
+ */
+
+static int doread(int fd, void *buf, size_t sz)
+{
+  char *p = buf;
+  while (sz) {
+    int r = read(fd, p, sz);
+    if (r < 0) {
+      if (errno == EINTR)
+       continue;
+      return (-1);
+    } else if (r == 0) {
+      errno = EIO;
+      return (-1);
+    }
+    sz -= r;
+    p += r;
+  }
+  return (0);
+}
+
+static int dowrite(int fd, const void *buf, size_t sz)
+{
+  const char *p = buf;
+  while (sz) {
+    int r = write(fd, p, sz);
+    if (r < 0) {
+      if (errno == EINTR)
+       continue;
+      return (-1);
+    } else if (r == 0) {
+      errno = EIO;
+      return (-1);
+    }
+    sz -= r;
+    p += r;
+  }
+  return (0);
+}
+
+/* --- @getstring@ --- *
+ *
+ * Arguments:  @int fd@ = file descriptor to read
+ *
+ * Returns:    String in heap-allocated block, or a null pointer.
+ *
+ * Use:                Decodes a string.
+ */
+
+static char *getstring(int fd)
+{
+  size_t sz;
+  char *p;
+
+  if (doread(fd, &sz, sizeof(sz)) || (p = malloc(sz + 1)) == 0)
+    return (0);
+  if (doread(fd, p, sz)) {
+    free(p);
+    return (0);
+  }
+  p[sz] = 0;
+  return (p);
+}
+
+/* --- @putstring@ --- *
+ *
+ * Arguments:  @int fd@ = file descriptor to write on
+ *             @const char *p@ = pointer to string to write
+ *
+ * Returns:    Zero if successful.
+ *
+ * Use:                Encodes a string.
+ */
+
+static int putstring(int fd, const char *p)
+{
+  size_t sz = strlen(p);
+  if (dowrite(fd, &sz, sizeof(sz)) || dowrite(fd, p, sz))
+    return (-1);
+  return (0);
+}
+
+/* --- @gethost@ --- *
+ *
+ * Arguments:  @int fd@ = file descriptor to read
+ *
+ * Returns:    Pointer to heap-allocated @struct hostent@, or null.
+ *
+ * Use:                Decodes a host structure.  The resulting structure is all in
+ *             one big heap block.
+ */
+
+#ifndef BRES_STANDALONE
+
+static struct hostent *gethost(int fd)
+{
+  hostskel hsk;
+  struct hostent *h;
+  char *name;
+  char **alias = 0;
+
+  /* --- Read the skeleton structure --- */
+
+  if (doread(fd, &hsk, sizeof(hsk)))
+    goto tidy_0;
+
+  /* --- Read the hostname and alias strings --- *
+   *
+   * Count the length of the strings as we go.
+   */
+
+  {
+    size_t sz =
+      sizeof(struct hostent) +
+      hsk.naddr * hsk.addrsz +
+      (hsk.naddr + hsk.nalias + 2) * sizeof(char *);
+
+    /* --- Read the primary host name --- */
+
+    if ((name = getstring(fd)) == 0)
+      goto tidy_0;
+    sz += strlen(name) + 1;
+
+    /* --- Read in the alias names --- */
+
+    if (hsk.nalias) {
+      int i;
+      if ((alias = malloc(hsk.nalias * sizeof(char *))) == 0)
+       goto tidy_1;
+      for (i = 0; i < hsk.nalias; i++)
+       alias[i] = 0;
+      for (i = 0; i < hsk.nalias; i++) {
+       if ((alias[i] = getstring(fd)) == 0)
+         goto tidy_2;
+       sz += strlen(alias[i]) + 1;
+      }
+    }
+
+    /* --- Allocate the output structure --- */
+
+    if ((h = malloc(sz)) == 0)
+      goto tidy_2;
+  }
+
+  /* --- Fill in the base structure --- */
+
+  h->h_addrtype = hsk.addrtype;
+  h->h_length = hsk.addrsz;
+
+  /* --- Start putting everything else in --- */
+
+  {
+    char **p = (char **)(h + 1);
+    char *a = (char *)(p + hsk.nalias + hsk.naddr + 2);
+    int i;
+
+    /* --- Start with the address table --- */
+
+    h->h_addr_list = p;
+    if (doread(fd, a, hsk.naddr * hsk.addrsz))
+      goto tidy_2;
+    for (i = 0; i < hsk.naddr; i++) {
+      *p++ = a;
+      a += hsk.addrsz;
+    }
+    *p++ = 0;
+
+    /* --- Finally copy the strings over --- */
+
+#define PUT(_p) do {                                                   \
+  size_t _len = strlen(_p) + 1;                                                \
+  memcpy(a, (_p), _len);                                               \
+  a += _len;                                                           \
+} while (0)
+
+    h->h_name = a;
+    PUT(name);
+    free(name);
+    h->h_aliases = p;
+    for (i = 0; i < hsk.nalias; i++) {
+      *p++ = a;
+      PUT(alias[i]);
+      free(alias[i]);
+    }
+    *p++ = 0;
+    free(alias);
+
+#undef PUT
+  }
+
+  return (h);
+
+  /* --- Tidy up after various types of failure --- */
+
+tidy_2:
+  {
+    int i;
+    for (i = 0; i < hsk.nalias && alias[i]; i++)
+      free(alias[i]);
+    free(alias);
+  }
+tidy_1:
+  free(name);
+tidy_0:
+  return (0);
+}
+
+#endif
+
+/* --- @puthost@ --- *
+ *
+ * Arguments:  @int fd@ = file descriptor
+ *             @struct hostent *h@ = pointer to host structure
+ *
+ * Returns:    Zero if successful.
+ *
+ * Use:                Encodes a host structure.
+ */
+
+static int puthost(int fd, struct hostent *h)
+{
+  hostskel hsk;
+  int i;
+
+  /* --- Fill in and send the skeleton structure --- */
+
+  for (i = 0; h->h_aliases[i]; i++)
+    ;
+  hsk.nalias = i;
+  for (i = 0; h->h_addr_list[i]; i++)
+    ;
+  hsk.naddr = i;
+  hsk.addrtype = h->h_addrtype;
+  hsk.addrsz = h->h_length;
+  if (dowrite(fd, &hsk, sizeof(hsk)))
+    return (-1);
+
+  /* --- Send the name and alias strings --- */
+
+  if (putstring(fd, h->h_name))
+    return (-1);
+  for (i = 0; h->h_aliases[i]; i++) {
+    if (putstring(fd, h->h_aliases[i]))
+      return (-1);
+  }
+
+  /* --- Send the address data --- */
+
+  for (i = 0; h->h_addr_list[i]; i++) {
+    if (dowrite(fd, h->h_addr_list[i], hsk.addrsz))
+      return (-1);
+  }
+
+  /* --- OK, done --- */
+
+  return (0);
+}
+
+/*----- Resolver server ---------------------------------------------------*/
+
+/* --- @child@ --- *
+ *
+ * Arguments:  @int rfd@ = output file descriptor for resolved hostnames
+ *             @int cfd@ = input file descriptor for raw addresses
+ *
+ * Returns:    Never.
+ *
+ * Use:                Asynchronous name resolving process.
+ */
+
+static void child(int rfd, int cfd)
+{
+  /* --- Close other file descriptors --- */
+
+  {
+    int i;
+#if defined(_SC_OPEN_MAX)
+    int maxfd = sysconf(_SC_OPEN_MAX);
+#elif defined(OPEN_MAX)
+    int maxfd = OPEN_MAX;
+#else
+    int maxfd = -1;
+#endif
+
+    if (maxfd < 0)
+      maxfd = 256; /* Fingers crossed... */
+    for (i = 0; i < maxfd; i++) {
+      if (i != rfd && i != cfd && i != 1)
+       close(i);
+    }
+  }
+
+  signal(SIGTERM, SIG_DFL);
+  signal(SIGHUP, SIG_DFL);
+  signal(SIGQUIT, SIG_DFL);
+  signal(SIGALRM, SIG_DFL);
+  signal(SIGINT, SIG_DFL);
+
+  /* --- Main request/response loop --- */
+
+  for (;;) {
+    int req, resp;
+    struct hostent *h;
+
+    /* --- Read the request --- */
+
+    if (doread(cfd, &req, sizeof(req)))
+      goto lose;
+
+    /* --- Process it into a host structure --- */
+
+    switch (req) {
+
+      /* --- Normal forward lookup --- */
+
+      case BRES_BYNAME: {
+       char *name = getstring(cfd);
+       if (!name)
+         goto lose;
+       h = gethostbyname(name);
+       free(name);
+      }        break;
+
+      /* --- Reverse lookup --- */
+
+      case BRES_BYADDR: {
+       struct in_addr addr;
+       char *p;
+       if (doread(cfd, &addr, sizeof(addr)))
+         goto lose;
+       if ((h = gethostbyaddr((char *)&addr, sizeof(addr), AF_INET)) == 0)
+         goto skip;
+
+       /* --- Do a forward lookup to confirm --- */
+
+       {
+         size_t sz = strlen(h->h_name) + 1;
+         if ((p = malloc(sz)) == 0)
+           goto skip;
+         memcpy(p, h->h_name, sz);
+       }
+
+       h = gethostbyname(p);
+       free(p);
+       if (!h)
+         goto skip;
+       if (h) {
+         char **pp;
+         for (pp = h->h_addr_list; *pp; pp++) {
+           struct in_addr a;
+           memcpy(&a, *pp, sizeof(a));
+           if (a.s_addr == addr.s_addr)
+             goto skip;
+         }
+       }
+       h = 0;
+       h_errno = NO_RECOVERY;
+      skip:;
+      }        break;
+
+      /* --- Unknown request -- may have lost sync --- */
+
+      default:
+       goto lose;
+    }
+
+    /* --- Transmit the response --- */
+
+    if (h) {
+      resp = BRES_HOSTENT;
+      if (dowrite(rfd, &resp, sizeof(resp)) || puthost(rfd, h))
+       goto lose;
+    } else {
+      resp = BRES_ERROR;
+      if (dowrite(rfd, &resp, sizeof(resp)) ||
+         dowrite(rfd, &h_errno, sizeof(h_errno)))
+       goto lose;
+    }
+  }
+
+lose:
+  _exit(1);
+}
+
+/* --- @main@ --- *
+ *
+ * Arguments:  @int argc@ = number of command line arguments
+ *             @char *argv[]@ = array of arguments
+ *
+ * Returns:    Runs until killed or an error occurs.
+ *
+ * Use:                A name resolver server process for mLib programs which need
+ *             this sort of thing.
+ */
+
+#ifdef BRES_STANDALONE
+
+int main(int argc, char *argv[])
+{
+  if (isatty(STDIN_FILENO)) {
+    char *p = strrchr(argv[0], '/');
+    if (p)
+      p++;
+    else
+      p = argv[0];
+    fprintf(stderr,
+           "%s: don't run this program unless you know what you're doing.\n",
+           p);
+    exit(1);
+  }
+  child(STDOUT_FILENO, STDIN_FILENO);
+  return (1);
+}
+
+#endif
+
+/*----- Main code ---------------------------------------------------------*/
+
+#ifndef BRES_STANDALONE
+
+/* --- @zap@ --- *
+ *
+ * Arguments:  @bres_server *rs@ = pointer to server block
+ *
+ * Returns:    ---
+ *
+ * Use:                Kills a server process, reaps the losing child and makes
+ *             things generally clean again.
+ */
+
+static void zap(bres_server *rs)
+{
+  /* --- Close the pipes, kill the child, and reap it --- */
+
+  if (rs->kid != -1) {
+    close(rs->fd);
+    close(rs->f.fd);
+    kill(rs->kid, SIGTERM);
+    waitpid(rs->kid, 0, 0);
+    rs->kid = -1;
+  }
+
+  /* --- Move the server to the back of the list --- */
+
+  if (!rs->rc) UNLINK(freelist, freetail, rs);
+  LINKTAIL(freelist, freetail, rs);
+}
+
+/* --- @bres_abort@ --- *
+ *
+ * Arguments:  @bres_client *rc@ = pointer to client block
+ *
+ * Returns:    ---
+ *
+ * Use:                Removes a queued job.
+ */
+
+void bres_abort(bres_client *rc)
+{
+  if (rc->q == BRES_BYNAME)
+    xfree(rc->u.name);
+  if (!rc->rs)
+    UNLINK(qhead, qtail, rc);
+  else {
+    sel_rmfile(&rc->rs->f);
+    zap(rc->rs);
+    rc->rs = 0;
+  }
+}
+
+/* --- @idle@ --- *
+ *
+ * Arguments:  @struct timeval *tv@ = pointer to the current time
+ *             @void *vp@ = pointer to a server block
+ *
+ * Returns:    ---
+ *
+ * Use:                Kills off a child which has been idle for too long.
+ */
+
+static void idle(struct timeval *tv, void *vp)
+{
+  bres_server *rs = vp;
+  zap(rs);
+}
+
+/* --- @answer@ --- *
+ *
+ * Arguments:  @int fd@ = file descriptor which is ready
+ *             @unsigned mode@ = what it's doing now
+ *             @void *vp@ = pointer to server block
+ *
+ * Returns:    ---
+ *
+ * Use:                Retrieves an answer from a name resolver process.
+ */
+
+static void attach(bres_client */*rc*/);
+
+static void answer(int fd, unsigned mode, void *vp)
+{
+  bres_server *rs = vp;
+  bres_client *rc = rs->rc;
+  struct hostent *h = 0;
+  int resp;
+  int fail = 1;
+
+  /* --- Report the result to my client --- */
+
+  sel_rmfile(&rs->f);
+  h_errno = -1;
+  if (doread(fd, &resp, sizeof(resp)) == 0) {
+    switch (resp) {
+      case BRES_ERROR:
+       doread(fd, &h_errno, sizeof(h_errno));
+       fail = 0;
+       break;
+      case BRES_HOSTENT:
+       h = gethost(fd);
+       fail = 0;
+       break;
+    }
+  }
+  if (rc) {
+    rc->func(h, rc->p);
+    if (rc->q == BRES_BYNAME)
+      xfree(rc->u.name);
+  }
+  if (h)
+    free(h);
+  if (fail)
+    zap(rs);
+  if (!rc)
+    return;
+
+  /* --- Wrap up the various structures --- */
+
+  rs->rc = 0;
+  rc->rs = 0;
+  LINKHEAD(freelist, freetail, rs);
+
+  /* --- Tie a timer onto the server block --- */
+
+  {
+    struct timeval tv;
+
+    gettimeofday(&tv, 0);
+    tv.tv_sec += BRES_IDLE;
+    sel_addtimer(sel, &rs->t, &tv, idle, rs);
+  }
+
+  /* --- If there are any clients waiting, attach one --- */
+
+  if (qhead) {
+    rc = qhead;
+    UNLINK(qhead, qtail, rc);
+    attach(rc);
+  }
+}
+
+/* --- @start@ --- *
+ *
+ * Arguments:  @bres_server *rs@ = pointer to a server block
+ *
+ * Returns:    Zero if OK, nonzero if something failed.
+ *
+ * Use:                Starts up a child resolver process.
+ */
+
+static int start(bres_server *rs)
+{
+  int rfd[2], cfd[2];
+  pid_t kid;
+  mdup_fd md[2];
+
+  /* --- Make the pipes --- */
+
+  if (pipe(rfd))
+    goto fail_0;
+  if (pipe(cfd))
+    goto fail_1;
+
+  /* --- Start up the child process --- */
+
+  if ((kid = fork()) < 0)
+    goto fail_2;
+  if (kid == 0) {
+    close(cfd[1]);
+    close(rfd[0]);
+
+    if (server) {
+      md[0].cur = cfd[0]; md[0].want = STDIN_FILENO;
+      md[1].cur = rfd[1]; md[1].want = STDOUT_FILENO;
+      if (mdup(md, 2) ||  execlp(server, server, (char *)0))
+       child(STDOUT_FILENO, STDIN_FILENO);
+    } else
+      child(rfd[1], cfd[0]);
+    _exit(1);
+  }
+
+  /* --- Fix up everything in the server block --- */
+
+  close(cfd[0]);
+  close(rfd[1]);
+  rs->fd = cfd[1];
+  sel_initfile(sel, &rs->f, rfd[0], SEL_READ, answer, rs);
+  rs->kid = kid;
+  return (0);
+
+  /* --- Fix up after errors --- */
+
+fail_2:
+  close(cfd[0]);
+  close(cfd[1]);
+fail_1:
+  close(rfd[0]);
+  close(rfd[1]);
+fail_0:
+  return (-1);
+}
+
+/* --- @attach@ --- *
+ *
+ * Arguments:  @bres_client *rc@ = pointer to a client block
+ *
+ * Returns:    ---
+ *
+ * Use:                Attaches a client to a spare server (which is assumed to
+ *             exist).
+ */
+
+static void attach(bres_client *rc)
+{
+  bres_server *rs;
+  int lose = 0;
+
+  /* --- Fix up the server ready for the job --- *
+   *
+   * If the server has a process, remove its timer.  Otherwise, fork off a
+   * new resolver process.  This is also where I go if I find that the child
+   * resolver process has lost while I wasn't looking.  Only one attempt at
+   * forking is performed.
+   */
+
+again:
+  rs = freelist;
+  if (rs->kid != -1)
+    sel_rmtimer(&rs->t);
+  else {
+    if (lose || start(rs))
+      goto lost;
+    lose = 1;
+  }
+
+  /* --- Submit the job to the resolver --- */
+
+  {
+    struct sigaction sa, osa;
+    int e;
+
+    /* --- Ignore @SIGPIPE@ for now --- *
+     *
+     * This way I can trap @EPIPE@ and reap a losing child, if there was one.
+     */
+
+    sa.sa_handler = SIG_IGN;
+    sa.sa_flags = 0;
+    sigemptyset(&sa.sa_mask);
+    sigaction(SIGPIPE, &sa, &osa);
+
+    /* --- Write the new job to the child --- */
+
+    e = 0;
+    if (dowrite(rs->fd, &rc->q, sizeof(rc->q)))
+      e = errno;
+    else switch (rc->q) {
+      case BRES_BYADDR:
+       if (dowrite(rs->fd, &rc->u.addr, sizeof(rc->u.addr)))
+         e = errno;
+       break;
+      case BRES_BYNAME:
+       if (putstring(rs->fd, rc->u.name))
+         e = errno;
+       break;
+    }
+    sigaction(SIGPIPE, &osa, 0);
+
+    /* --- Sort out various errors --- *
+     *
+     * This was once more complicated, handling @EPIPE@ separately from other
+     * errors.  Now everything's handled the same way.
+     */
+
+    if (e) {
+      zap(rs);
+      goto again;
+    }
+  }
+
+  /* --- Fiddle with lists so that everything's OK --- */
+
+  sel_addfile(&rs->f);
+  UNLINK(freelist, freetail, rs);
+  rs->rc = rc;
+  rc->rs = rs;
+  return;
+
+lost:
+  rc->func(0, rc->p);
+  if (rc->q == BRES_BYNAME)
+    xfree(rc->u.name);
+}
+
+/* --- @resolve@ --- *
+ *
+ * Arguments:  @bres_client *rc@ = pointer to filled-in client block
+ *
+ * Returns:    ---
+ *
+ * Use:                Dispatcher for incoming resolution jobs.
+ */
+
+static void resolve(bres_client *rc)
+{
+  /* --- If there's a free server, plug it in --- */
+
+  rc->rs = 0;
+  if (freelist)
+    attach(rc);
+  else
+    LINKTAIL(qhead, qtail, rc);
+}
+
+/* --- @bres_byaddr@ --- *
+ *
+ * Arguments:  @bres_client *rc@ = pointer to client block
+ *             @struct in_addr addr@ = address to resolve
+ *             @void (*func)(struct hostent *h, void *p)@ = handler function
+ *             @void *p@ = argument for handler function
+ *
+ * Returns:    ---
+ *
+ * Use:                Adds an address lookup job to the queue.  The job will be
+ *             processed when there's a spare resolver process to deal with
+ *             it.
+ */
+
+void bres_byaddr(bres_client *rc, struct in_addr addr,
+                void (*func)(struct hostent */*h*/, void */*p*/),
+                void *p)
+{
+  rc->q = BRES_BYADDR;
+  rc->u.addr = addr;
+  rc->func = func;
+  rc->p = p;
+  resolve(rc);
+}
+
+/* --- @bres_byname@ --- *
+ *
+ * Arguments:  @bres_client *rc@ = pointer to client block
+ *             @const char *name@ = name to resolve
+ *             @void (*func)(struct hostent *h, void *p)@ = handler function
+ *             @void *p@ = argument for handler function
+ *
+ * Returns:    ---
+ *
+ * Use:                Adds a name lookup job to the queue.  The job will be
+ *             processed when there's a spare resolver process to deal with
+ *             it.
+ */
+
+void bres_byname(bres_client *rc, const char *name,
+                void (*func)(struct hostent */*h*/, void */*p*/),
+                void *p)
+{
+  rc->q = BRES_BYNAME;
+  rc->u.name = xstrdup(name);
+  rc->func = func;
+  rc->p = p;
+  resolve(rc);
+}
+
+/* --- @bres_exec@ --- *
+ *
+ * Arguments:  @const char *file@ = file containing server code or null
+ *
+ * Returns:    ---
+ *
+ * Use:                Makes `bres' use a standalone server rather than copies of
+ *             the current process.  This can reduce memory consumption for
+ *             large processes, at the expense of startup time (which
+ *             shouldn't be too bad anyway, because of the resolver design).
+ *             If the filename is null, a default set up at install time is
+ *             used.  It's probably a good idea to leave it alone.
+ */
+
+void bres_exec(const char *file)
+{
+  if (file)
+    server = file;
+  else
+    server = BRES_SERVER;
+}
+
+/* --- @bres_init@ --- *
+ *
+ * Arguments:  @sel_state *s@ = pointer to select multiplexor
+ *
+ * Returns:    ---
+ *
+ * Use:                Initializes the background resolver for use.
+ */
+
+void bres_init(sel_state *s)
+{
+  int i;
+
+  sel = s;
+  for (i = 0; i < BRES_MAX; i++) {
+    servers[i].kid = -1;
+    servers[i].rc = 0;
+    LINKTAIL(freelist, freetail, &servers[i]);
+  }
+}
+
+#endif
+
+/*----- That's all, folks -------------------------------------------------*/
diff --git a/sel/bres.h b/sel/bres.h
new file mode 100644 (file)
index 0000000..8234172
--- /dev/null
@@ -0,0 +1,171 @@
+/* -*-c-*-
+ *
+ * Background reverse name resolution
+ *
+ * (c) 1999 Straylight/Edgeware
+ */
+
+/*----- Licensing notice --------------------------------------------------*
+ *
+ * This file is part of the mLib utilities library.
+ *
+ * mLib is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * mLib is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with mLib; if not, write to the Free
+ * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+ * MA 02111-1307, USA.
+ */
+
+#ifndef MLIB_BRES_H
+#define MLIB_BRES_H
+
+#ifdef __cplusplus
+  extern "C" {
+#endif
+
+/*----- Header files ------------------------------------------------------*/
+
+#include <sys/types.h>
+
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+
+#ifdef HAVE_ADNS
+#  include <adns.h>
+#endif
+
+#ifndef MLIB_SEL_H
+#  include "sel.h"
+#endif
+
+#ifndef MLIB_SELBUF_H
+#  include "selbuf.h"
+#endif
+
+/*----- Data structures ---------------------------------------------------*/
+
+/* --- Client allocated request block --- */
+
+typedef struct bres_client {
+#ifdef HAVE_ADNS
+  adns_query aq;                       /* ADNS query handle */
+  adns_answer *a;                      /* Answer for reverse resolution */
+  struct _unused *_pad1;               /* And a spare slot */
+#else
+  struct bres_client *next, *prev;     /* Queue of waiting resolve jobs */
+  struct bres_server *rs;              /* Pointer to attached server */
+#endif
+  int q;                               /* Query type (name or address) */
+  union {
+    struct in_addr addr;               /* Address to resolve */
+    char *name;                                /* Name to resolve */
+  } u;
+  void (*func)(struct hostent */*h*/, void */*p*/); /* Handler function */
+  void *p;                             /* Argument for handler function */
+} bres_client;
+
+/* --- Server maintained resolver blocks --- */
+
+typedef struct bres_server {
+  struct bres_server *next, *prev;     /* Doubly-linked list of servers */
+  pid_t kid;                           /* Process id of server process */
+  int fd;                              /* File descriptors */
+  struct bres_client *rc;              /* Pointer to attached client */
+  sel_timer t;                         /* Timeout for idle servers */
+  sel_file f;                          /* Read selector for server */
+} bres_server;
+
+/*----- Functions provided ------------------------------------------------*/
+
+/* --- @bres_abort@ --- *
+ *
+ * Arguments:  @bres_client *rc@ = pointer to client block
+ *
+ * Returns:    ---
+ *
+ * Use:                Removes a queued job.
+ */
+
+extern void bres_abort(bres_client */*rc*/);
+
+/* --- @bres_byaddr@ --- *
+ *
+ * Arguments:  @bres_client *rc@ = pointer to client block
+ *             @struct in_addr addr@ = address to resolve
+ *             @void (*func)(struct hostent *h, void *p)@ = handler function
+ *             @void *p@ = argument for handler function
+ *
+ * Returns:    ---
+ *
+ * Use:                Adds an address lookup job to the queue.  The job will be
+ *             processed when there's a spare resolver process to deal with
+ *             it.
+ */
+
+extern void bres_byaddr(bres_client */*rc*/, struct in_addr /*addr*/,
+                       void (*/*func*/)(struct hostent */*h*/, void */*p*/),
+                       void */*p*/);
+
+/* --- @bres_byname@ --- *
+ *
+ * Arguments:  @bres_client *rc@ = pointer to client block
+ *             @const char *name@ = name to resolve
+ *             @void (*func)(struct hostent *h, void *p)@ = handler function
+ *             @void *p@ = argument for handler function
+ *
+ * Returns:    ---
+ *
+ * Use:                Adds a name lookup job to the queue.  The job will be
+ *             processed when there's a spare resolver process to deal with
+ *             it.
+ */
+
+extern void bres_byname(bres_client */*rc*/, const char */*name*/,
+                       void (*/*func*/)(struct hostent */*h*/, void */*p*/),
+                       void */*p*/);
+
+/* --- @bres_exec@ --- *
+ *
+ * Arguments:  @const char *file@ = file containing server code or null
+ *
+ * Returns:    ---
+ *
+ * Use:                Makes `bres' use a standalone server rather than copies of
+ *             the current process.  This can reduce memory consumption for
+ *             large processes, at the expense of startup time (which
+ *             shouldn't be too bad anyway, because of the resolver design).
+ *             If the filename is null, a default set up at install time is
+ *             used.  It's probably a good idea to leave it alone.
+ */
+
+extern void bres_exec(const char */*file*/);
+
+/* --- @bres_init@ --- *
+ *
+ * Arguments:  @sel_state *s@ = pointer to select multiplexor
+ *
+ * Returns:    ---
+ *
+ * Use:                Initializes the background resolver for use.
+ */
+
+extern void bres_init(sel_state */*s*/);
+
+/*----- That's all, folks -------------------------------------------------*/
+
+#ifdef __cplusplus
+  }
+#endif
+
+#endif
diff --git a/sel/conn.3 b/sel/conn.3
new file mode 100644 (file)
index 0000000..1c91193
--- /dev/null
@@ -0,0 +1,123 @@
+.\" -*-nroff-*-
+.TH conn 3 "23 May 1999" "Straylight/Edgeware" "mLib utilities library"
+.\" @conn_fd
+.\" @conn_init
+.\" @conn_kill
+.SH NAME
+conn \- selector for nonblocking connections
+.SH SYNOPSIS
+.nf
+.B "#include <mLib/conn.h>"
+
+.BI "int conn_fd(conn *" c ", sel_state *" s ", int " fd ,
+.BI "            void (*" func ")(int " fd ", void *" p ),
+.BI "            void *" p );
+
+.BI "int conn_init(conn *" c ", sel_state *" s ", int " fd ,
+.BI "              struct sockaddr *" dst ", int " dsz ,
+.BI "              void (*" func ")(int " fd ", void *" p ),
+.BI "              void *" p );
+
+.BI "void conn_kill(conn *" c );
+.fi
+.SH DESCRIPTION
+The
+.B conn
+selector manages a nonblocking connection to a remote socket.  The
+selector's state is maintained in an object of type
+.BR conn .
+.PP
+Before use, a
+.B conn
+selector must be initialized.  This requires a call to
+.B conn_init
+with a fairly large number of arguments:
+.TP
+.BI "conn *" c
+Pointer to
+.B conn
+object which needs to be initialized.
+.TP
+.BI "sel_state *" s
+Pointer to a multiplexor object (type
+.BR sel_state )
+to which this selector should be attached.  See
+.BR sel (3)
+for more details about multiplexors, and how this whole system works.
+.TP
+.BI "int " fd
+File descriptor for the socket you want to connect.  This becomes the
+`property' of the
+.B conn
+selector until the connection attempt finishes.  For example, if there's
+an error, the descriptor will be closed.
+.TP
+.BI "struct sockaddr *" dst
+Pointer to destination socket address for the connection.  Make sure
+that the address has the right family.
+.TP
+.BI "int " dsz
+Size of the destination socket address.
+.TP
+.BI "void (*" func ")(int " fd ", void *" p )
+A function to call when the connection is complete.  It is passed the
+file descriptor of the connected socket, and the pointer passed
+to
+.B conn_init
+as the
+.I p
+argument.
+.TP
+.BI "void *" p
+An arbitrary pointer whose value is passed to the handler function when
+the connection finishes.
+.PP
+A few words are in order about
+.BR conn_init 's
+detailed behaviour and return value.  If it returns \-1, the connection
+attempt has failed immediately, an error code is stored in the global
+variable
+.BR errno ,
+the file descriptor has been
+.IR closed ,
+and the connection function will
+.I not
+be called.  If it returns zero, then there has been no immediate
+failure; the connection function
+.I might
+have been called, if the connection succeeded immediately, but it will
+certainly be called some time, unless the connector is killed (see
+.B conn_kill
+below).  When the connection function is called, it will either be
+passed the file descriptor of the new-connected socket (to indicate
+success) or the value \-1 for failure; in the latter case, an
+appropriate error code is stored in
+.BR errno .
+.PP
+Alternatively, if you have a socket with a pending connection (i.e., a
+call to
+.BR connect
+returned \-1 and set
+.B errno
+to
+.BR EINPROGRESS ),
+you can call
+.BR conn_fd.
+Its arguments are the same as for
+.BR conn_init ,
+except that since the socket knows its a peer address the
+.I dst
+and
+.I dsz
+arguments are not given, and it can't fail.
+.PP
+If you want to cancel the connection attempt before it finishes, call
+.B conn_kill
+with the address of the selector.  The file descriptor is closed, and
+the selector becomes safe to be discarded.
+.SH "SEE ALSO"
+.BR connect (2),
+.BR sel (3),
+.BR mLib (3).
+.SH AUTHOR
+Mark Wooding, <mdw@distorted.org.uk>
diff --git a/sel/conn.c b/sel/conn.c
new file mode 100644 (file)
index 0000000..aee27d4
--- /dev/null
@@ -0,0 +1,167 @@
+/* -*-c-*-
+ *
+ * Nonblocking connect handling
+ *
+ * (c) 1999 Straylight/Edgeware
+ */
+
+/*----- Licensing notice --------------------------------------------------*
+ *
+ * This file is part of the mLib utilities library.
+ *
+ * mLib is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * mLib is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with mLib; if not, write to the Free
+ * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+ * MA 02111-1307, USA.
+ */
+
+/*----- Header files ------------------------------------------------------*/
+
+#include <errno.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <sys/types.h>
+#include <sys/socket.h>
+
+#include <unistd.h>
+#include <fcntl.h>
+
+#include "conn.h"
+#include "sel.h"
+
+/*----- Main code ---------------------------------------------------------*/
+
+/* --- @conn_connect@ --- *
+ *
+ * Arguments:  @int fd@ = file descriptor to try to connect
+ *             @unsigned mode@ = what we can do to the file
+ *             @void *p@ = pointer to connection context
+ *
+ * Returns:    ---
+ *
+ * Use:                Handles select results for pending connections.
+ */
+
+static void conn_connect(int fd, unsigned mode, void *p)
+{
+#ifndef PATH_MAX
+#  define PATH_MAX 1024
+#endif
+
+  conn *c = p;
+  char buf[PATH_MAX + 8]; /* Big enough */
+  socklen_t sinsz;
+
+  sinsz = sizeof(buf);
+  sel_rmfile(&c->writer);
+  if (getpeername(fd, (struct sockaddr *)buf, &sinsz) < 0) {
+    int err;
+    socklen_t errsz = sizeof(err);
+    if (getsockopt(fd, SOL_SOCKET, SO_ERROR, &err, &errsz) == 0)
+      errno = err;
+    close(fd);
+    c->func(-1, c->p);
+  } else
+    c->func(fd, c->p);
+}
+
+/* --- @conn_fd@ --- *
+ *
+ * Arguments:  @conn *c@ = pointer to connection block
+ *             @sel_state *s@ = pointer to select state to attach to
+ *             @int fd@ = file descriptor of socket
+ *             @void (*func)(int fd, void *p) = handler function
+ *             @void *p@ = argument for the handler function
+ *
+ * Returns:    ---
+ *
+ * Use:                Sets up a nonblocking connect job.  The socket should have a
+ *             connect pending for it already.
+ */
+
+void conn_fd(conn *c, sel_state *s, int fd,
+            void (*func)(int /*fd*/, void */*p*/),
+            void *p)
+{
+  c->func = func;
+  c->p = p;
+  sel_initfile(s, &c->writer, fd, SEL_WRITE, conn_connect, c);
+  sel_addfile(&c->writer);
+}
+
+/* --- @conn_init@ --- *
+ *
+ * Arguments:  @conn *c@ = pointer to connection block
+ *             @sel_state *s@ = pointer to select state to attach to
+ *             @int fd@ = file descriptor of socket to connect
+ *             @struct sockaddr *dst@ = destination address
+ *             @int dsz@ = size of destination address
+ *             @void (*func)(int fd, void *p) = handler function
+ *             @void *p@ = argument for the handler function
+ *
+ * Returns:    Zero on success, nonzero on failure.
+ *
+ * Use:                Sets up a nonblocking connect job.  The socket should already
+ *             be bound if you care about that sort of thing.  When the
+ *             connection completes, the handler function is called with the
+ *             connected socket as an argument.  If the connect fails rather
+ *             than completes, the socket is closed, and the handler is
+ *             informed of this by being passed a negative file descriptor.
+ *             In either case, the select job is then removed.
+ */
+
+int conn_init(conn *c, sel_state *s, int fd,
+             struct sockaddr *dst, int dsz,
+             void (*func)(int /*fd*/, void */*p*/),
+             void *p)
+{
+  int f;
+
+  if ((f = fcntl(fd, F_GETFL)) < 0 || fcntl(fd, F_SETFL, f | O_NONBLOCK))
+    goto fail;
+
+  if (!connect(fd, dst, dsz))
+    func(fd, p);
+  else if (errno != EINPROGRESS)
+    goto fail;
+  else
+    conn_fd(c, s, fd, func, p);
+  return (0);
+
+fail:
+  close(fd);
+  return (-1);
+}
+
+/* --- @conn_kill@ --- *
+ *
+ * Arguments:  @conn *c@ = pointer to connection to dispose of
+ *
+ * Returns:    ---
+ *
+ * Use:                Disposes of a connection when it's not wanted any more.
+ */
+
+void conn_kill(conn *c)
+{
+  if (c->writer.fd != -1) {
+    close(c->writer.fd);
+    sel_rmfile(&c->writer);
+    c->writer.fd = -1;
+  }
+}
+
+/*----- That's all, folks -------------------------------------------------*/
diff --git a/sel/conn.h b/sel/conn.h
new file mode 100644 (file)
index 0000000..53f4321
--- /dev/null
@@ -0,0 +1,118 @@
+/* -*-c-*-
+ *
+ * Nonblocking connect handling
+ *
+ * (c) 1999 Straylight/Edgeware
+ */
+
+/*----- Licensing notice --------------------------------------------------*
+ *
+ * This file is part of the mLib utilities library.
+ *
+ * mLib is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * mLib is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with mLib; if not, write to the Free
+ * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+ * MA 02111-1307, USA.
+ */
+
+#ifndef MLIB_CONN_H
+#define MLIB_CONN_H
+
+#ifdef __cplusplus
+  extern "C" {
+#endif
+
+/*----- Header files ------------------------------------------------------*/
+
+#include <sys/types.h>
+#include <sys/socket.h>
+
+#ifndef MLIB_SEL_H
+#  include "sel.h"
+#endif
+
+/*----- Data structures ---------------------------------------------------*/
+
+/* --- The nonblocking connect structure --- */
+
+typedef struct conn {
+  sel_file writer;                     /* Select listener */
+  void (*func)(int /*fd*/, void */*p*/); /* Handler function */
+  void *p;                             /* Argument for handler function */
+} conn;
+
+/*----- Functions provided ------------------------------------------------*/
+
+/* --- @conn_fd@ --- *
+ *
+ * Arguments:  @conn *c@ = pointer to connection block
+ *             @sel_state *s@ = pointer to select state to attach to
+ *             @int fd@ = file descriptor of socket
+ *             @void (*func)(int fd, void *p) = handler function
+ *             @void *p@ = argument for the handler function
+ *
+ * Returns:    ---
+ *
+ * Use:                Sets up a nonblocking connect job.  The socket should have a
+ *             connect pending for it already.
+ */
+
+void conn_fd(conn */*c*/, sel_state */*s*/, int /*fd*/,
+            void (*/*func*/)(int /*fd*/, void */*p*/),
+            void */*p*/);
+
+/* --- @conn_init@ --- *
+ *
+ * Arguments:  @conn *c@ = pointer to connection block
+ *             @sel_state *s@ = pointer to select state to attach to
+ *             @int fd@ = file descriptor of socket to connect
+ *             @struct sockaddr *dst@ = destination address
+ *             @int dsz@ = size of destination address
+ *             @void (*func)(int fd, void *p) = handler function
+ *             @void *p@ = argument for the handler function
+ *
+ * Returns:    Zero on success, nonzero on failure.
+ *
+ * Use:                Sets up a nonblocking connect job.  The socket should already
+ *             be bound if you care about that sort of thing.  When the
+ *             connection completes, the handler function is called with the
+ *             connected socket as an argument.  If the connect fails rather
+ *             than completes, the socket is closed, and the handler is
+ *             informed of this by being passed a negative file descriptor.
+ *             In either case, the select job is then removed.
+ */
+
+extern int conn_init(conn */*c*/, sel_state */*s*/, int /*fd*/,
+                    struct sockaddr */*dst*/, int /*dsz*/,
+                    void (*/*func*/)(int /*fd*/, void */*p*/),
+                    void */*p*/);
+
+/* --- @conn_kill@ --- *
+ *
+ * Arguments:  @conn *c@ = pointer to connection to dispose of
+ *
+ * Returns:    ---
+ *
+ * Use:                Disposes of a connection when it's not wanted any more.  The
+ *             connect handler function is not called.
+ */
+
+extern void conn_kill(conn */*c*/);
+
+/*----- That's all, folks -------------------------------------------------*/
+
+#ifdef __cplusplus
+  }
+#endif
+
+#endif
diff --git a/sel/ident.3 b/sel/ident.3
new file mode 100644 (file)
index 0000000..5247bd4
--- /dev/null
@@ -0,0 +1,120 @@
+.\" -*-nroff-*-
+.TH ident 3 "2 October 1999" "Straylight/Edgeware" "mLib utilities library"
+.SH "NAME"
+ident \- identd (RFC931) client
+.\" @ident_abort
+.\" @ident
+.\" @ident_socket
+.SH "SYNOPSIS"
+.nf
+.B "#include <mLib/ident>"
+
+.BI "void ident_abort(ident_request *" rq );
+.BI "void ident(ident_request *" rq ", sel_state *" s ,
+.BI "           const struct sockaddr_in *" local ,
+.BI "           const struct sockaddr_in *" remote ,
+.BI "           void (*" func ")(ident_reply *" i ", void *" p ),
+.BI "           void *" p );
+.BI "void ident_socket(ident_request *" rq ", sel_state *" s ", int " sk ,
+.BI "                  void (*" func ")(ident_reply *" i ", void *" p ),
+.BI "                  void *" p );
+.fi
+.SH "DESCRIPTION"
+The
+.B ident.h
+header defines some types and functions which implement an ident client
+(as specified by RFC931).
+.PP
+The state of an ident request in progress is represented in an object of
+type
+.BR ident_request ,
+a structure type whose layout is unspecified.  Storage for these objects
+is provided by the caller.
+.PP
+The primary interface for starting an ident request is the
+.B ident
+function.  It takes a number of arguments:
+.TP
+.BI "ident_request *" rq
+Pointer to the client request block which is to maintain the state of
+the request as it progresses.  This must not be discarded while the
+request is in progress, for obvious reasons.
+.TP
+.BI "sel_state *" s
+Pointer to an I/O multiplexor.  See
+.BR sel (3)
+for more information about the I/O multiplexing system.
+.TP
+.BI "struct sockaddr_in *" local ", *" remote
+The local and remote socket addresses describing the connection to be
+enquired about.  The local address is not optional.  If you don't have
+it handy, you can use
+.B ident_socket
+described below.
+.TP
+.BI "void (*" func ")(ident_reply *" i ", void *" p )
+The handler function to be called with the result of the ident request.
+The
+.B ident_reply
+structure is described in detail below.
+.TP
+.BI "void *" p
+A pointer argument to be supplied to the handler function.
+.PP
+The
+.B ident_socket
+function provides an alternative interface to setting up an ident
+request.  Instead of the local and remote socket addresses, the function
+works out the local and remote addresses from a socket file descriptor
+provided as an argument.
+.PP
+The handler function is provided the results in a structure of type
+.BR ident_reply .
+The pointer may be null if there was a problem connecting to the server;
+in this case, the global
+.B errno
+variable describes the problem in its usual inimitable way.
+.PP
+The reply structure contains the following members:
+.TP
+.B "unsigned short sport, dport"
+The source and destination ports, as seen from the point of view of the
+server.  These should match up with the ports in the request structure.
+.TP
+.B "unsigned type"
+The type of response received from the server.  There are three possible
+values:
+.B IDENT_USERID
+indicates that the server specified a userid and operating system name;
+.B IDENT_ERROR
+indicates that the server reported an error message; and
+.B IDENT_BAD
+indicates that the server's response was invalid.
+.TP
+.B "char *u.userid.os"
+The name of the remote operating system; only valid if
+.B type
+has the value
+.BR IDENT_USERID .
+.TP
+.B "char *u.userid.user"
+The name of the remote user; only valid if
+.B type
+has the value
+.BR IDENT_USERID .
+.TP
+.B "char *u.error"
+The error message reported by the server; only valid if
+.B type
+has the value
+.BR IDENT_ERROR .
+.PP
+An ident request in progress can be aborted by calling
+.B ident_abort
+on the request block.  In this case, no notification is made to the
+handler function.
+.SH "SEE ALSO"
+.BR sel (3),
+.BR mLib (3).
+.SH "AUTHOR"
+Mark Wooding, <mdw@distorted.org.uk>
diff --git a/sel/ident.c b/sel/ident.c
new file mode 100644 (file)
index 0000000..a59cd22
--- /dev/null
@@ -0,0 +1,366 @@
+/* -*-c-*-
+ *
+ * Nonblocking RFC931 client
+ *
+ * (c) 1999 Mark Wooding
+ */
+
+/*----- Licensing notice --------------------------------------------------*
+ *
+ * This file is part of the mLib utilities library.
+ *
+ * mLib is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * mLib is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with mLib; if not, write to the Free
+ * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+ * MA 02111-1307, USA.
+ */
+
+/*----- Header files ------------------------------------------------------*/
+
+#include <ctype.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+
+#include "conn.h"
+#include "dstr.h"
+#include "exc.h"
+#include "ident.h"
+#include "macros.h"
+#include "selbuf.h"
+
+/*----- Main code ---------------------------------------------------------*/
+
+/* --- @next@ --- *
+ *
+ * Arguments:  @char **pp@ = address of string pointer
+ *
+ * Returns:    Address of next token.
+ *
+ * Use:                Reads the next token from the result string.  Tokens are
+ *             terminated by whitespace, or `:' or `,' characters.  The
+ *             result string has had `\' escapes removed; it's stored in
+ *             the same memory as the input string.  The actual content
+ *             of the delimiter doesn't seem to be interesting, so it
+ *             gets thrown away.
+ */
+
+static char *next(char **pp)
+{
+  char *p, *q, *r;
+
+  /* --- Deal with reads past the end of the string --- */
+
+  if (!*pp)
+    return ("");
+
+  /* --- Initialize various pointers into the string --- */
+
+  p = q = r = *pp;
+
+  /* --- Skip past any leading whitespace --- */
+
+  while (ISSPACE(*p))
+    p++;
+
+  /* --- Now start work on the string itself --- */
+
+  for (;;) {
+    if (*p == 0 || *p == ':' || *p == ',' || ISSPACE(*p))
+      break;
+    else if (*p == '\\') {
+      p++;
+      if (!*p) {
+       *q++ = '\\';
+       break;
+      }
+    }
+    *q++ = *p++;
+  }
+
+  /* --- Tidy up afterwards --- */
+
+  while (ISSPACE(*p))
+    p++;
+  if (*p == 0)
+    *pp = 0;
+  else if (*p == ':' || *p == ',')
+    *pp = p + 1;
+  else
+    *pp = p;
+  *q = 0;
+
+  return (r);
+}
+
+/* --- @parse@ --- *
+ *
+ * Arguments:  @char *p@ = pointer to input string from identd
+ *             @ident_reply *i@ = pointer to output block
+ *
+ * Returns:    ---
+ *
+ * Use:                Parses a result string from an RFC931 (identd) server.
+ */
+
+static void parse(char *p, ident_reply *i)
+{
+  char *q;
+
+  /* --- Read the source and destination port numbers --- */
+
+  i->sport = atoi(next(&p));
+  i->dport = atoi(next(&p));
+
+  /* --- Find out what sort of a reply this is --- */
+
+  q = next(&p);
+  if (STRCMP(q, ==, "USERID")) {
+    i->type = IDENT_USERID;
+    i->u.userid.os = next(&p);
+    i->u.userid.user = next(&p);
+  } else if (STRCMP(q, ==, "ERROR")) {
+    i->type = IDENT_ERROR;
+    i->u.error = next(&p);
+  } else
+    i->type = IDENT_BAD;
+}
+
+/* --- @line@ --- *
+ *
+ * Arguments:  @char *s@ = pointer to string from ident server
+ *             @size_t len@ = length of the line
+ *             @void *p@ = pointer to my request block
+ *
+ * Returns:    ---
+ *
+ * Use:                Handles a string from an ident server.
+ */
+
+static void line(char *s, size_t len, void *p)
+{
+  ident_request *rq = p;
+
+  rq->state = IDENT_DONE;
+  close(rq->b.reader.fd);
+  if (!s)
+    rq->func(0, rq->p);
+  else {
+    ident_reply i;
+    parse(s, &i);
+    rq->func(&i, rq->p);
+  }
+  selbuf_destroy(&rq->b);
+}
+
+/* --- @connected@ --- *
+ *
+ * Arguments:  @int fd@ = file descriptor
+ *             @void *p@ = pointer to request block
+ *
+ * Returns:    ---
+ *
+ * Use:                Handles a connection to a remote ident server.
+ */
+
+static void connected(int fd, void *p)
+{
+  ident_request *rq = p;
+  dstr d = DSTR_INIT;
+
+  /* --- Handle an error during the connect --- */
+
+  if (fd < 0)
+    goto fail_0;
+
+  /* --- Initialize the string to send to the remote host --- */
+
+  TRY {
+    dstr_putf(&d, "%u, %u\r\n",
+             ntohs(rq->remote.sin_port), ntohs(rq->local.sin_port));
+  } CATCH switch (exc_type) {
+    case EXC_NOMEM:
+      EXIT_TRY;
+      goto fail_1;
+    default:
+      RETHROW;
+  } END_TRY;
+
+  /* --- Do the rest of the work --- */
+
+  if (write(fd, d.buf, d.len) < d.len)
+    goto fail_1;
+  dstr_destroy(&d);
+  rq->state = IDENT_READ;
+  selbuf_init(&rq->b, rq->s, fd, line, rq);
+  return;
+
+  /* --- Tidy up after misfortunes --- */
+
+fail_1:
+  dstr_destroy(&d);
+  close(fd);
+fail_0:
+  rq->state = IDENT_DONE;
+  rq->func(0, rq->p);
+}
+
+/* --- @ident_abort@ --- *
+ *
+ * Arguments:  @ident_request *rq@ = pointer to request block
+ *
+ * Returns:    ---
+ *
+ * Use:                Cancels an ident request in progress.
+ */
+
+void ident_abort(ident_request *rq)
+{
+  switch (rq->state) {
+    case IDENT_CONN:
+      conn_kill(&rq->c);
+      break;
+    case IDENT_READ:
+      close(rq->b.reader.fd);
+      selbuf_destroy(&rq->b);
+      break;
+  }
+}
+
+/* --- @go@ --- *
+ *
+ * Arguments:  @ident_request *rq@ = pointer to request block
+ *
+ * Returns:    ---
+ *
+ * Use:                Starts a connection to the remote ident server.
+ */
+
+static void go(ident_request *rq)
+{
+  int fd;
+  struct sockaddr_in sin;
+  int opt;
+
+  /* --- Create the socket I'll use --- */
+
+  if ((fd = socket(PF_INET, SOCK_STREAM, 0)) < 0)
+    goto fail_0;
+  memset(&sin, 0, sizeof(sin));
+  sin.sin_family = AF_INET;
+  sin.sin_port = 0;
+  sin.sin_addr = rq->local.sin_addr;
+  if (bind(fd, (struct sockaddr *)&sin, sizeof(sin)))
+    goto fail_1;
+
+  /* --- Out-of-band data would confuse us --- */
+
+  opt = 1;
+  setsockopt(fd, SOL_SOCKET, SO_OOBINLINE, &opt, sizeof(opt));
+
+  /* --- Start a connection to the remote server --- */
+
+  sin.sin_family = AF_INET;
+  sin.sin_port = htons(113);
+  sin.sin_addr = rq->remote.sin_addr;
+  if (conn_init(&rq->c, rq->s, fd, (struct sockaddr *)&sin, sizeof(sin),
+               connected, rq))
+    goto fail_0;
+
+  /* --- Finish off initializing the block --- */
+
+  rq->state = IDENT_CONN;
+  return;
+
+  /* --- Tidy up after lossage --- */
+
+fail_1:
+  close(fd);
+fail_0:
+  rq->state = IDENT_DONE;
+  rq->func(0, rq->p);
+}
+
+/* --- @ident@ --- *
+ *
+ * Arguments:  @ident_request *rq@ = pointer to request block
+ *             @sel_state *s@ = I/O multiplexor
+ *             @const struct sockaddr_in *local, *remote@ = addresses
+ *             @void (*func)(ident_reply *i, void *p)@ = handler function
+ *             @void *p@ = argument for handler
+ *
+ * Returns:    ---
+ *
+ * Use:                Initializes an ident request.
+ */
+
+void ident(ident_request *rq, sel_state *s,
+          const struct sockaddr_in *local,
+          const struct sockaddr_in *remote,
+          void (*func)(ident_reply */*i*/, void */*p*/),
+          void *p)
+{
+  memcpy(&rq->local, local, sizeof(rq->local));
+  memcpy(&rq->remote, remote, sizeof(rq->remote));
+  rq->func = func;
+  rq->p = p;
+  rq->s = s;
+  go(rq);
+}
+
+/* --- @ident_socket@ --- *
+ *
+ * Arguments:  @ident_request *rq@ = pointer to request block
+ *             @sel_state *s@ = I/O multiplexor
+ *             @int sk@ = connected socket file descriptor
+ *             @void (*func)(ident_reply *i, void *p)@ = handler function
+ *             @void *p@ = argument for handler
+ *
+ * Returns:    ---
+ *
+ * Use:                An alternative interface to @ident@.  Initializes an ident
+ *             request from a connected socket, rather than from an explicit
+ *             address.  This will call @getsockname@ and @getpeername@ to
+ *             find out what the socket is actually connected to, which adds
+ *             convenience but wastes time.
+ */
+
+void ident_socket(ident_request *rq, sel_state *s, int sk,
+                 void (*func)(ident_reply */*i*/, void */*p*/),
+                 void *p)
+{
+  socklen_t sinsz;
+  if ((sinsz = sizeof(struct sockaddr_in),
+       getsockname(sk, (struct sockaddr *)&rq->local, &sinsz)) ||
+      (sinsz = sizeof(struct sockaddr_in),
+       getpeername(sk, (struct sockaddr *)&rq->remote, &sinsz))) {
+    rq->state = IDENT_DONE;
+    func(0, p);
+    return;
+  }
+
+  rq->func = func;
+  rq->p = p;
+  rq->s = s;
+  go(rq);
+}
+
+/*----- That's all, folks -------------------------------------------------*/
diff --git a/sel/ident.h b/sel/ident.h
new file mode 100644 (file)
index 0000000..97e979f
--- /dev/null
@@ -0,0 +1,155 @@
+/* -*-c-*-
+ *
+ * Nonblocking RFC931 client
+ *
+ * (c) 1999 Mark Wooding
+ */
+
+/*----- Licensing notice --------------------------------------------------*
+ *
+ * This file is part of the mLib utilities library.
+ *
+ * mLib is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * mLib is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with mLib; if not, write to the Free
+ * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+ * MA 02111-1307, USA.
+ */
+
+#ifndef MLIB_IDENT_H
+#define MLIB_IDENT_H
+
+#ifdef __cplusplus
+  extern "C" {
+#endif
+
+/*----- Header files ------------------------------------------------------*/
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+#ifndef MLIB_CONN_H
+#  include "conn.h"
+#endif
+
+#ifndef MLIB_SEL_H
+#  include "sel.h"
+#endif
+
+#ifndef MLIB_SELBUF_H
+#  include "selbuf.h"
+#endif
+
+/*----- Data structures ---------------------------------------------------*/
+
+/* --- Parsed response from ident server --- */
+
+typedef struct ident_reply {
+  unsigned short sport, dport;         /* Source and destination ports */
+  unsigned type;                       /* Type of reply from server */
+  union {
+    struct {
+      char *os;                                /* Operating system name */
+      char *user;                      /* User name */
+    } userid;
+    char *error;                       /* Error message from server */
+  } u;
+} ident_reply;
+
+/* --- Response type codes --- */
+
+enum {
+  IDENT_USERID,
+  IDENT_ERROR,
+  IDENT_BAD
+};
+
+/* --- Request structure --- */
+
+typedef struct ident_request {
+  struct sockaddr_in local, remote;
+  unsigned state;
+  void (*func)(ident_reply */*i*/, void */*p*/);
+  void *p;
+  sel_state *s;
+  conn c;
+  selbuf b;
+} ident_request;
+
+enum {
+  IDENT_CONN,
+  IDENT_READ,
+  IDENT_DONE
+};
+
+/*----- Functions provided ------------------------------------------------*/
+
+/* --- @ident_abort@ --- *
+ *
+ * Arguments:  @ident_request *rq@ = pointer to request block
+ *
+ * Returns:    ---
+ *
+ * Use:                Cancels an ident request in progress.
+ */
+
+extern void ident_abort(ident_request */*rq*/);
+
+/* --- @ident@ --- *
+ *
+ * Arguments:  @ident_request *rq@ = pointer to request block
+ *             @sel_state *s@ = I/O multiplexor
+ *             @const struct sockaddr_in *local, *remote@ = addresses
+ *             @void (*func)(ident_reply *i, void *p)@ = handler function
+ *             @void *p@ = argument for handler
+ *
+ * Returns:    ---
+ *
+ * Use:                Initializes an ident request.
+ */
+
+extern void ident(ident_request */*rq*/, sel_state */*s*/,
+                 const struct sockaddr_in */*local*/,
+                 const struct sockaddr_in */*remote*/,
+                 void (*/*func*/)(ident_reply */*i*/, void */*p*/),
+                 void */*p*/);
+
+/* --- @ident_socket@ --- *
+ *
+ * Arguments:  @ident_request *rq@ = pointer to request block
+ *             @sel_state *s@ = I/O multiplexor
+ *             @int sk@ = connected socket file descriptor
+ *             @void (*func)(ident_reply *i, void *p)@ = handler function
+ *             @void *p@ = argument for handler
+ *
+ * Returns:    ---
+ *
+ * Use:                An alternative interface to @ident@.  Initializes an ident
+ *             request from a connected socket, rather than from an explicit
+ *             address.  This will call @getsockname@ and @getpeername@ to
+ *             find out what the socket is actually connected to, which adds
+ *             convenience but wastes time.
+ */
+
+extern void ident_socket(ident_request */*rq*/, sel_state */*s*/, int /*sk*/,
+                        void (*/*func*/)(ident_reply */*i*/, void */*p*/),
+                        void */*p*/);
+
+/*----- That's all, folks -------------------------------------------------*/
+
+#ifdef __cplusplus
+  }
+#endif
+
+#endif
diff --git a/sel/sel.3 b/sel/sel.3
new file mode 100644 (file)
index 0000000..a1f86a5
--- /dev/null
+++ b/sel/sel.3
@@ -0,0 +1,394 @@
+.\" -*-nroff-*-
+.TH sel 3 "22 May 1999" "Straylight/Edgeware" "mLib utilities library"
+.SH NAME
+sel \- low level interface for waiting for I/O
+.\" @sel_init
+.\" @sel_initfile
+.\" @sel_addfile
+.\" @sel_force
+.\" @sel_rmfile
+.\" @sel_addtimer
+.\" @sel_rmtimer
+.\" @sel_addhook
+.\" @sel_rmhook
+.\" @sel_fdmerge
+.\" @sel_select
+.SH SYNOPSIS
+.nf
+.B "#include <mLib/sel.h>"
+
+.BI "void sel_init(sel_state *" s );
+
+.BI "void sel_initfile(sel_state *" s ", sel_file *" f ,
+.BI "                  int " fd ", unsigned " mode ,
+.BI "                  void (*" func ")(int " fd ", unsigned " mode ", void *" p ),
+.BI "                  void *" p );
+.BI "void sel_addfile(sel_file *" f );
+.BI "void sel_force(sel_file *" f );
+.BI "void sel_rmfile(sel_file *" f );
+
+.BI "void sel_addtimer(sel_state *" s ", sel_timer *" t ,
+.BI "                  struct timeval *" tv ,
+.BI "                  void (*" func ")(struct timeval *" tv ", void *" p ),
+.BI "                  void *" p );
+.BI "void sel_rmtimer(sel_timer *" t );
+
+.BI "void sel_addhook(sel_state *" s ", sel_hook *" h ,
+.BI "                 sel_hookfn " before ", sel_hookfn " after ,
+.BI "                 void *" p );
+.BI "void sel_rmhook(sel_hook *" h );
+
+.BI "int sel_fdmerge(fd_set *" dest ", fd_set *" fd ", int " maxfd );
+
+.BI "int sel_select(sel_state *" s );
+.fi
+.SH "OVERVIEW"
+The
+.B sel
+subsystem provides a structured way of handling I/O in a non-blocking
+event-driven sort of a way, for single-threaded programs.  (Although
+there's no reason at all why multithreaded programs shouldn't use
+.BR sel ,
+it's much less useful.)
+.PP
+The
+.B sel
+subsystem does no memory allocation, and has no static state.  All
+of its data is stored in structures allocated by the caller.  I'll
+explain how this fits in nicely with typical calling sequences below.
+.PP
+Although all the data structures are exposed in the header file, you
+should consider
+.BR sel 's
+data structures to be opaque except where described here, and not fiddle
+around inside them.  Some things may become more sophisticated later.
+.SH "IMPORTANT CONCEPTS"
+The system is based around two concepts:
+.I multiplexors
+and
+.IR selectors .
+.PP
+A
+.I selector
+is interested in some sort of I/O event, which might be something like
+`my socket has become readable', or `the time is now half past three on
+the third of June 2013'.  It has a handler function attached to it,
+which is called when the appropriate event occurs.  Some events happen
+once only ever; some events happen over and over again.  For example, a
+socket might become readable many times, but it's only half-past three
+on the third of June 2013 once.
+.PP
+When a selector is initialized, the caller describes the event the
+selector is interested in, and specifies which function should handle
+the event.  Also, it must specify an arbitrary pointer which is passed
+to the handler function when the event occurs.  This is typically some
+sort of pointer to instance data of some kind, providing more
+information about the event (`it's
+.I this
+socket that's become readable'), or what to do about it.
+.PP
+A multiplexor gathers information about who's interested in what.  It
+maintains lists of selectors.  Selectors must be added to a
+mulitplexor before the events they're interested in are actually watched
+for.  Selectors can be removed again when their events aren't
+interesting any more.  Apart from adding and removing selectors, you can
+.I select
+on a multiplexor.  This waits for something interesting to happen and
+then fires off all the selectors which have events waiting for them.
+.PP
+You can have lots of multiplexors in your program if you like.  You can
+only ask for events from one of them at a time, though.
+.PP
+There are currently two types of selector understood by the low-level
+.B sel
+system: file selectors and timer selectors.  These two types of
+selectors react to corresponding different types of events.  A file
+event indicates that a file is now ready for reading or writing.  A
+timer event indicates that a particular time has now passed (useful for
+implementing timeouts).  More sophisticated selectors can be constructed
+using
+.BR sel 's
+interface.  For examples, see
+.BR selbuf (3)
+and
+.BR conn (3).
+.SH "PROGRAMMING INTERFACE"
+.SS "Multiplexors"
+A multiplexor is represented using the type
+.B sel_state
+defined in the
+.B <mLib/sel.h>
+header file.  Before use, a
+.B sel_state
+must be initialized, by passing it to the
+.B sel_init
+function.  The header file talks about `state blocks' a lot \- that's
+because it was written before I thought the word `multiplexor' was
+nicer.
+.PP
+File selectors are represented by the type
+.BR sel_file .
+The interface provides three operations on file selectors:
+initialization, addition to multiplexor, and removal from a
+multiplexor.  It's convenient to separate addition and removal from
+initialization because file selectors often get added and removed many
+times over during their lifetimes.
+.SS "File selectors"
+A file selector is initialized by the
+.B sel_initfile
+function.  This requires a large number of arguments:
+.TP
+.BI "sel_state *" s
+A pointer to the multiplexor with which the file selector will be
+associated.  This is stored in the selector so that the multiplexor
+argument can be omitted from later calls.
+.TP
+.BI "sel_file *" f
+Pointer to the file selector object to be initialized.
+.TP
+.BI "int " fd
+The file descriptor which the selector is meant to watch.
+.TP
+.BI "unsigned " mode
+A constant describing which condition the selector is interested in.
+This must be one of the
+.B SEL_
+constants described below.
+.TP
+.BI "void (*" func ")(int " fd ", unsigned " mode ", void *" p );
+The handler function which is called when the appropriate condition
+occurs on the file.  This function's interface is described in more
+detail below.
+.TP
+.BI "void *" p
+An arbitrary pointer argument passed to
+.I func
+when it's called.  Beyond this, no meaning is attached to the value of
+the pointer.  If you don't care about it, just leave it as null.
+.PP
+The mode argument is one of the following constants:
+.TP
+.B SEL_READ
+Raise an event when the file is ready to be read from.
+.TP
+.B SEL_WRITE
+Raise an event when the file is ready to be written to.
+.TP
+.B SEL_EXC
+Raise an event when the file has an `exceptional condition'.
+.PP
+The constant
+.B SEL_MODES
+contains the number of possible file modes.  This is useful internally
+for allocating arrays of the right size.
+.PP
+The functions
+.B sel_addfile
+and
+.B sel_rmfile
+perform the addition and removal operations on file selectors.  They are
+passed only the actual selector object, since the selector already knows
+which multiplexor it's associated with.  A newly initialized file
+selector is not added to its multiplexor: this must be done explicitly.
+.PP
+The handler function for a file multiplexor is passed three arguments:
+the file descriptor for the file, a mode argument which describes the
+file's new condition, and the pointer argument set up at initialization
+time.
+.PP
+The function
+.B sel_force
+will sometimes be useful while a
+.B sel_select
+call (see below) is in progress.  It marks a file selector as being
+ready even if it's not really.  This is most useful when dynamically
+adding a write selector: it's likely that the write will succeed
+immediately, so it's worth trying.  This will only work properly if
+the write is non-blocking.
+.PP
+The member
+.B fd
+of the
+.B sel_file
+structure is exported.  It contains the file descriptor in which the
+selector is interested.  You may not modify this value, but it's useful
+to be able to read it out \- it saves having to keep a copy.
+.SS "Timer selectors"
+Timer selectors are simpler.  There are only two operations provided on
+timer selectors: addition and removal.  Initialization is performed as
+part of the addition operation.
+.PP
+A timer selector is represented by an object of time
+.BR sel_timer .
+.PP
+The function
+.B sel_addtimer
+requires lots of arguments:
+.TP
+.BI "sel_state *" s
+Pointer to the multiplexor to which the selector is to be added.
+.TP
+.BI "sel_timer *" t
+Pointer to the timer selector object being initialized and added.
+.TP
+.BI "struct timeval " tv
+When the selector should raise its event.  This is an
+.I absolute
+time, not a relative time as required by the traditional
+.BR select (2)
+and
+.BR poll (2)
+system calls.
+.TP
+.BI "void (*" func ")(struct timeval *" tv ", void *" p )
+A handler function to be called when the event occurs.  The function is
+passed the
+.I current
+time, and the arbitrary pointer passed to
+.B sel_addtimer
+as the
+.I p
+argument.
+.TP
+.BI "void *" p
+A pointer passed to
+.I func
+when the timer event occurs.  Beyond this, the value of the pointer is
+not inspected.
+.PP
+The function
+.B sel_rmtimer
+removes a timer selector.  It is passed only the selector object.
+.PP
+Note that timer events are a one-shot thing.  Once they've happened, the
+timer selector is removed and the event can't happen again.  This is
+normally what you want.  Removing a timer is only useful (or safe!)
+before the timer event has been sent.
+.SS "Performing I/O"
+Finally, the function
+.B sel_select
+is passed a multiplexor object.  It waits for something interesting to
+happen, informs the appropriate selector handlers, and returns.  If
+everything went according to plan,
+.B sel_select
+returns zero.  Otherwise it returns \-1, and the global variable
+.B errno
+is set appropriately.
+.SS "Hook functions"
+In order to interface other I/O multiplexing systems to this one, it's
+possible to register
+.I hook
+functions which are called before and after each
+.BR select (2)
+system call.
+.PP
+The function
+.B sel_addhook
+registers a pair of hook functions.  It is passed the pointer to the
+multiplexor which is being hooked, the address of a
+.B sel_hook
+structure which will be used to record the hook information, the two
+hook functions (either of which may be a null pointer, signifying no
+action to be taken), and a pointer argument to be passed to the hook
+functions.
+.PP
+The function
+.B sel_rmhook
+removes a pair of hooks given the address of the
+.B sel_hook
+structure which recorded their registration.
+.PP
+A
+.I "hook function"
+is passed three arguments:
+.TP
+.BI "sel_state *" s
+A pointer to the multiplexor block.  This probably isn't very useful,
+actually.
+.TP
+.BI "sel_args *" a
+A pointer to a block containing proposed arguments for, or results from,
+.BR select (2).
+The format of this block is described below.
+.TP
+.BI "void *" p
+A pointer argument set up in the call to
+.B sel_addhook
+to provide the hook function with some context.
+.PP
+The argument block contains the following members:
+.TP
+.B "int maxfd"
+One greater than the highest-numbered file descriptor to be examined.
+This may need to be modified if the file descriptor sets are altered.
+.TP
+.B "fd_set fd[SEL_MODES]"
+A file descriptor set for each of
+.BR SEL_READ ,
+.B SEL_WRITE
+and
+.BR SEL_EXC .
+Before the
+.B select
+call, these may be modified to register an interest in other file
+descriptors.  Afterwards, they may be examined to decide which file
+descriptors are active.
+.TP
+.B "struct timeval tv, *tvp"
+Before the
+.B select
+call, these specify the time after which to return even if no files are
+active.  If
+.B tvp
+is null, there is no timeout, and
+.B select
+should wait forever if necessary.  Otherwise
+.B tvp
+should contain the address of
+.BR tv ,
+and
+.B tv
+should contain the timeout.  After the
+.B select
+call, the contents of
+.B tv
+are undefined.
+.TP
+.B "struct timeval now"
+Before the
+.B select
+call, contains the current time.  After the call, this will have been
+updated to reflect the new current time only if there was a timeout
+set before the call.
+.PP
+Hook functions may find the call
+.B sel_fdmerge
+useful.  Given two file descriptor sets
+.I dest
+and
+.IR fd ,
+and a possibly overestimated highest file descriptor in
+.IR fd ,
+the function sets in
+.I dest
+all of the descriptors set in
+.I fd
+and returns an accurate file descriptor count as its result.
+.SH "OTHER NOTES"
+Although the naming seems to suggest that this is all
+based around the BSD-ish
+.BR select (2)
+system call (and indeed it is), the interface is actually a good deal
+more general than that.  An implementation which worked off System V-ish
+.BR poll (2)
+instead would be possible to make, and would look just the same from the
+outside.  Some work would be needed to make the hook functions work,
+though.
+.SH "SEE ALSO"
+.BR select (2),
+.BR poll (2),
+.BR conn (3),
+.BR selbuf (3),
+.BR mLib (3).
+.SH AUTHOR
+Mark Wooding, <mdw@distorted.org.uk>
diff --git a/sel/sel.c b/sel/sel.c
new file mode 100644 (file)
index 0000000..434a74e
--- /dev/null
+++ b/sel/sel.c
@@ -0,0 +1,451 @@
+/* -*-c-*-
+ *
+ * I/O multiplexing support
+ *
+ * (c) 1999 Straylight/Edgeware
+ */
+
+/*----- Licensing notice --------------------------------------------------*
+ *
+ * This file is part of the mLib utilities library.
+ *
+ * mLib is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * mLib is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with mLib; if not, write to the Free
+ * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+ * MA 02111-1307, USA.
+ */
+
+/*----- Header files ------------------------------------------------------*/
+
+#include <assert.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <sys/types.h>
+#include <sys/time.h>
+#include <unistd.h>
+
+#include "sel.h"
+#include "sub.h"
+#include "tv.h"
+
+/*----- Data structures ---------------------------------------------------*/
+
+typedef struct sel_pendfile {
+  struct sel_pendfile *next;
+  sel_file *f;
+} pfile;
+
+typedef struct sel_pendtimer {
+  struct sel_pendtimer *next;
+  sel_timer *t;
+} ptimer;
+
+/*----- Main code ---------------------------------------------------------*/
+
+/* --- @sel_init@ --- *
+ *
+ * Arguments:  @sel_state *s@ = pointer to a state block to initialize
+ *
+ * Returns:    ---
+ *
+ * Use:                Initializes a select state block.
+ */
+
+void sel_init(sel_state *s)
+{
+  int i;
+
+  for (i = 0; i < SEL_MODES; i++) {
+    s->files[i] = 0;
+    FD_ZERO(&s->fd[i]);
+  }
+  s->timers = 0;
+  s->hooks = 0;
+  s->args = 0;
+}
+
+/* --- @sel_initfile@ --- *
+ *
+ * Arguments:  @sel_state *s@ = select state to attach to
+ *             @sel_file *f@ = pointer to a file block to initialize
+ *             @int fd@ = the file descriptor to listen to
+ *             @unsigned mode@ = what to listen for
+ *             @void (*func)(int fd, unsigned mode, void *p)@ = handler
+ *             @void *p@ = argument to pass to handler
+ *
+ * Returns:    ---
+ *
+ * Use:                Initializes a file block ready for use.  The file block
+ *             isn't added to the list of things to do until a call to
+ *             @sel_addfile@.
+ */
+
+void sel_initfile(sel_state *s, sel_file *f,
+                 int fd, unsigned mode,
+                 void (*func)(int /*fd*/, unsigned /*mode*/, void */*p*/),
+                 void *p)
+{
+  f->s = s;
+  f->fd = fd;
+  f->mode = mode;
+  f->func = func;
+  f->p = p;
+  f->pend = 0;
+}
+
+/* --- @sel_addfile@ --- *
+ *
+ * Arguments:  @sel_file *f@ = pointer to a file block
+ *
+ * Returns:    ---
+ *
+ * Use:                Adds a file block into the list of things to listen to.
+ */
+
+void sel_addfile(sel_file *f)
+{
+  sel_file **ff = &f->s->files[f->mode];
+
+  /* --- This little dance looks like line-noise, but it does the job --- */
+
+  while (*ff && (*ff)->fd > f->fd)
+    ff = &(*ff)->next;
+  f->next = *ff;
+  f->prev = ff;
+  if (*ff)
+    (*ff)->prev = &f->next;
+  *ff = f;
+  FD_SET(f->fd, f->s->fd + f->mode);
+}
+
+/* --- @sel_force@ --- *
+ *
+ * Arguments:  @sel_file *f@ = pointer to file selector
+ *
+ * Returns:    ---
+ *
+ * Use:                Forces a file selector to be considered ready.  This is only
+ *             useful during a call to @sel_select@.  Of particular use is
+ *             forcing a write selector when there's something interesting
+ *             ready for it.
+ */
+
+void sel_force(sel_file *f)
+{
+  if (f->s->args)
+    FD_SET(f->fd, &f->s->args->fd[f->mode]);
+}
+
+/* --- @sel_rmfile@ --- *
+ *
+ * Arguments:  @sel_file *f@ = pointer to a file block
+ *
+ * Returns:    ---
+ *
+ * Use:                Removes a file block from the list of things to listen to.
+ */
+
+void sel_rmfile(sel_file *f)
+{
+  *f->prev = f->next;
+  if (f->next)
+    f->next->prev = f->prev;
+  FD_CLR(f->fd, f->s->fd + f->mode);
+  if (f->pend) {
+    f->pend->f = 0;
+    f->pend = 0;
+  }
+}
+
+/* --- @sel_addtimer@ --- *
+ *
+ * Arguments:  @sel_state *s@ = pointer to a state block
+ *             @sel_timer *t@ = pointer to a timer block
+ *             @struct timeval *tv@ = pointer to time to activate
+ *             @void (*func)(struct timeval *tv, void *p)@ = handler
+ *             @void *p@ = argument for handler function
+ *
+ * Returns:    ---
+ *
+ * Use:                Registers and sets up a timer.
+ */
+
+void sel_addtimer(sel_state *s, sel_timer *t,
+                 struct timeval *tv,
+                 void (*func)(struct timeval */*tv*/, void */*p*/),
+                 void *p)
+{
+  sel_timer **tt = &s->timers;
+  { sel_timer *q; for (q = s->timers; q; q = q->next) assert(q != t); }
+
+  /* --- Set up the timer block --- */
+
+  t->tv = *tv;
+  t->func = func;
+  t->p = p;
+  t->pend = 0;
+
+  /* --- More line noise --- */
+
+  while (*tt && TV_CMP(&(*tt)->tv, <, tv))
+    tt = &(*tt)->next;
+  t->next = *tt;
+  t->prev = tt;
+  if (*tt)
+    (*tt)->prev = &t->next;
+  *tt = t;
+}
+
+/* --- @sel_rmtimer@ --- *
+ *
+ * Arguments:  @sel_timer *t@ = pointer to timer block
+ *
+ * Returns:    ---
+ *
+ * Use:                Removes a timer from the list of timers.
+ */
+
+void sel_rmtimer(sel_timer *t)
+{
+  if (t->pend) {
+    t->pend->t = 0;
+    t->pend = 0;
+  } else {
+    *t->prev = t->next;
+    if (t->next)
+      t->next->prev = t->prev;
+  }
+}
+
+/* --- @sel_addhook@ --- *
+ *
+ * Arguments:  @sel_state *s@ = pointer to state block
+ *             @sel_hook *h@ = pointer to hook block
+ *             @sel_hookfn before, after@ = hook functions
+ *             @void *p@ = pointer argument to pass to hook functions
+ *
+ * Returns:    ---
+ *
+ * Use:                Registers hook functions to be called on each select call.
+ */
+
+void sel_addhook(sel_state *s, sel_hook *h,
+                sel_hookfn before, sel_hookfn after,
+                void *p)
+{
+  h->before = before;
+  h->after = after;
+  h->p = p;
+  h->next = s->hooks;
+  h->prev = &s->hooks;
+  if (s->hooks)
+    s->hooks->prev = &h->next;
+  s->hooks = h;
+}
+
+/* --- @sel_rmhook@ --- *
+ *
+ * Arguments:  @sel_hook *h@ = pointer to hook block
+ *
+ * Returns:    ---
+ *
+ * Use:                Removes hook functions.
+ */
+
+void sel_rmhook(sel_hook *h)
+{
+  if (h->next)
+    h->next->prev = h->prev;
+  *h->prev = h->next;
+}
+
+/* --- @sel_fdmerge@ --- *
+ *
+ * Arguments:  @fd_set *dest@ = destination FD set
+ *             @fd_set *fd@ = pointer to set to merge
+ *             @int maxfd@ = highest numbered descriptor in @fd@ + 1
+ *
+ * Returns:    Actual highest numbered descriptor.
+ *
+ * Use:                Merges file descriptor sets, and returns an accurate @maxfd@
+ *             value.
+ */
+
+int sel_fdmerge(fd_set *dest, fd_set *fd, int maxfd)
+{
+  int i, m = -1;
+
+  for (i = 0; i < maxfd; i++) {
+    if (FD_ISSET(i, fd)) {
+      FD_SET(i, dest);
+      m = i;
+    }
+  }
+
+  return (m + 1);
+}
+
+/* --- @sel_select@ --- *
+ *
+ * Arguments:  @sel_state *s@ = pointer to state block
+ *
+ * Returns:    Zero if all OK, -1 on error.
+ *
+ * Use:                Does a @select@ call (or equivalent @poll@).
+ */
+
+int sel_select(sel_state *s)
+{
+  sel_args a;
+  int err;
+
+  /* --- Initialize the argument block --- */
+
+  {
+    int i;
+    a.maxfd = 0;
+    for (i = 0; i < SEL_MODES; i++) {
+      if (s->files[i] && s->files[i]->fd >= a.maxfd)
+       a.maxfd = s->files[i]->fd + 1;
+    }
+  }
+
+  memcpy(a.fd, s->fd, sizeof(a.fd));
+  if (s->timers || s->hooks)
+    gettimeofday(&a.now, 0);
+  if (!s->timers)
+    a.tvp = 0;
+  else {
+    if (TV_CMP(&s->timers->tv, >, &a.now))
+      TV_SUB(&a.tv, &s->timers->tv, &a.now);
+    else {
+      a.tv.tv_sec = 0;
+      a.tv.tv_usec = 0;
+    }
+    a.tvp = &a.tv;
+  }
+  s->args = &a;
+
+  /* --- Grind through the pre hooks --- */
+
+  {
+    sel_hook *h = s->hooks;
+    while (h) {
+      sel_hook *hh = h;
+      h = h->next;
+      if (hh->before)
+       hh->before(s, &a, hh->p);
+    }
+  }
+
+  /* --- Run the @select@ call --- */
+
+  if ((err = select(a.maxfd,
+                   &a.fd[SEL_READ], &a.fd[SEL_WRITE], &a.fd[SEL_EXC],
+                   a.tvp)) < 0) {
+    s->args = 0;
+    return (err);
+  }
+
+  if (a.tvp)
+    gettimeofday(&a.now, 0);
+
+  /* --- Run through the hooks again --- */
+
+  {
+    sel_hook *h = s->hooks;
+    while (h) {
+      sel_hook *hh = h;
+      h = h->next;
+      if (hh->after)
+       hh->after(s, &a, hh->p);
+    }
+  }
+
+  /* --- Run through the timers --- */
+
+  if (s->timers) {
+    ptimer *pthead, *pt, **ptt = &pthead;
+    sel_timer *t;
+
+    for (t = s->timers; t && TV_CMP(&t->tv, <=, &a.now); t = t->next) {
+      pt = CREATE(ptimer);
+      pt->t = t;
+      t->pend = pt;
+      *ptt = pt;
+      ptt = &pt->next;
+    }
+    *ptt = 0;
+    if (t) {
+      *t->prev = 0;
+      t->prev = &s->timers;
+    }
+    s->timers = t;
+    while (pthead) {
+      pt = pthead;
+      pthead = pt->next;
+      t = pt->t;
+      if (t) {
+       t->pend = 0;
+       t->next = 0;
+       t->prev = &t->next;
+       t->func(&a.now, t->p);
+      }
+      DESTROY(pt);
+    }
+  }
+
+  /* --- And finally run through the files --- *
+   *
+   * Do reads first.  It's quite possible that a read might prompt a write,
+   * but the other way around is less likely.  Fortunately, the modes are
+   * in the right order for this.
+   */
+
+  {
+    int i;
+
+    for (i = 0; i < SEL_MODES; i++) {
+      pfile *pfhead, *pf, **pff = &pfhead;
+      sel_file *f;
+
+      for (f = s->files[i]; f; f = f->next) {
+       if (!FD_ISSET(f->fd, &a.fd[i]))
+         continue;
+       pf = CREATE(pfile);
+       pf->f = f;
+       f->pend = pf;
+       *pff = pf;
+       pff = &pf->next;
+      }
+      *pff = 0;
+      while (pfhead) {
+       pf = pfhead;
+       pfhead = pf->next;
+       f = pf->f;
+       if (f) {
+         f->pend = 0;
+         f->func(f->fd, i, f->p);
+       }
+       DESTROY(pf);
+      }
+    }
+  }
+
+  s->args = 0;
+  return (0);
+}
+
+/*----- That's all, folks -------------------------------------------------*/
diff --git a/sel/sel.h b/sel/sel.h
new file mode 100644 (file)
index 0000000..b1534be
--- /dev/null
+++ b/sel/sel.h
@@ -0,0 +1,308 @@
+/* -*-c-*-
+ *
+ * I/O multiplexing support
+ *
+ * (c) 1999 Straylight/Edgeware
+ */
+
+/*----- Licensing notice --------------------------------------------------*
+ *
+ * This file is part of the mLib utilities library.
+ *
+ * mLib is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * mLib is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with mLib; if not, write to the Free
+ * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+ * MA 02111-1307, USA.
+ */
+
+#ifndef MLIB_SEL_H
+#define MLIB_SEL_H
+
+#ifdef __cplusplus
+  extern "C" {
+#endif
+
+/*----- Theory lesson -----------------------------------------------------*
+ *
+ * Things which are expecting to do I/O or go off at a certain time are
+ * called `selectors'.  There are two types of selectors: `file selectors'
+ * wait patiently for a file to become readable or writable; `timeout
+ * selectors' wait for a certain amount of time to elapse.  There is also a
+ * `multiplexor' which copes with managing all of this stuff.
+ *
+ * Multiplexors aren't actually very interesting.  You initialize them with
+ * @sel_init@, and then add and remove selectors as you go.  When you want to
+ * wait for something to happen, call @sel_select@.
+ *
+ * A file selector can *either* read *or* write.  It can't do both.  This is
+ * because you quite often want to read a socket but not write it; during
+ * those times you don't want to write, you just don't install a write
+ * selector.
+ *
+ * File selectors are called when their files are available for reading or
+ * writing as appropriate, and given their file descriptor, the state of the
+ * file, and a pointer that was registered along with the selector.
+ *
+ * File selectors are set up in two phases.  First, they're `initialized'
+ * with @sel_initfile@.  An initialized file selector doesn't do anything.
+ * It needs to be added to a multiplexor using `sel_addfile'.  It can later
+ * be removed using `sel_rmfile'.  You can carry on adding and removing as
+ * you wish.  Just don't try adding it twice in a row.
+ *
+ * Timeout selectors are called at a certain time.  (Actually, they're called
+ * *after* a certain time.)  There's no separate initialization step with
+ * timouts: you just add them and they work.  If you decide you don't want a
+ * timeout to go off, then you just remove it.  (Adding and removing the
+ * *same* timeout isn't something you do very often.  You usually use a
+ * different expiry time each time.)
+ */
+
+/*----- Header files ------------------------------------------------------*/
+
+#include <sys/types.h>
+#include <sys/time.h>
+#include <unistd.h>
+
+/*----- Data structures ---------------------------------------------------*/
+
+/* --- A multiplexor --- *
+ *
+ * The files are sorted in reverse order of file descriptor number; the
+ * timers are in normal order of occurrence.  Thus, the interesting one
+ * is always at the front of the list.
+ */
+
+enum {
+  SEL_READ,                            /* File is ready to read */
+  SEL_WRITE,                           /* File is ready to write */
+  SEL_EXC,                             /* Something odd has happened */
+  SEL_MODES                            /* Number of modes available */
+};
+
+typedef struct sel_state {
+  struct sel_file *files[SEL_MODES];   /* Lists of interesting files */
+  struct sel_timer *timers;            /* List of timers */
+  struct sel_hook *hooks;              /* List of hook functions applied */
+  fd_set fd[SEL_MODES];                        /* Quick reference table for files */
+  struct sel_args *args;               /* Pointer to arguments */
+} sel_state;
+
+/* --- Listening for a file --- */
+
+typedef struct sel_file {
+  struct sel_file *next;               /* Next file in the list */
+  struct sel_file **prev;              /* Previous file in the list */
+  struct sel_state *s;                 /* Pointer to select multiplexor */
+  int fd;                              /* File descriptor */
+  unsigned mode;                       /* Interesting event for file */
+  void (*func)(int /*fd*/, unsigned /*mode*/, void */*p*/); /* Handler */
+  void *p;                             /* Argument for the handler */
+  struct sel_pendfile *pend;           /* Pending file information */
+} sel_file;
+
+/* --- Waiting for a timeout --- */
+
+typedef struct sel_timer {
+  struct sel_timer *next;              /* Next timer in the list */
+  struct sel_timer **prev;             /* Previous timer in the list */
+  struct timeval tv;                   /* Real time when timer should go */
+  void (*func)(struct timeval */*tv*/, void */*p*/); /* Handler function */
+  void *p;                             /* Argument for the handler */
+  struct sel_pendtimer *pend;          /* Pending timer information */
+} sel_timer;
+
+/* --- A select argument block --- */
+
+typedef struct sel_args {
+  int maxfd;                           /* Highest-numbered file */
+  fd_set fd[SEL_MODES];                        /* Bit flags for all the files */
+  struct timeval tv, *tvp;             /* Time to return */
+  struct timeval now;                  /* Current time */
+} sel_args;
+
+/* --- A selector hook --- *
+ *
+ * The hooks are called (in arbitrary order) on each select.
+ */
+
+typedef void (*sel_hookfn)(sel_state */*s*/,
+                          sel_args */*a*/,
+                          void */*p*/);
+
+typedef struct sel_hook {
+  struct sel_hook *next;               /* Next hook in the list */
+  struct sel_hook **prev;              /* Previous hook in the list */
+  sel_hookfn before, after;            /* Hook functions */
+  void *p;                             /* Argument for the hook functions */
+} sel_hook;
+
+/*----- Functions provided ------------------------------------------------*/
+
+/* --- @sel_init@ --- *
+ *
+ * Arguments:  @sel_state *s@ = pointer to a state block to initialize
+ *
+ * Returns:    ---
+ *
+ * Use:                Initializes a select state block.
+ */
+
+extern void sel_init(sel_state */*s*/);
+
+/* --- @sel_initfile@ --- *
+ *
+ * Arguments:  @sel_state *s@ = select state to attach to
+ *             @sel_file *f@ = pointer to a file block to initialize
+ *             @int fd@ = the file descriptor to listen to
+ *             @unsigned mode@ = what to listen for
+ *             @void (*func)(int fd, unsigned mode, void *p)@ = handler
+ *             @void *p@ = argument to pass to handler
+ *
+ * Returns:    ---
+ *
+ * Use:                Initializes a file block ready for use.  The file block
+ *             isn't added to the list of things to do until a call to
+ *             @sel_addfile@.
+ */
+
+extern void sel_initfile(sel_state */*s*/, sel_file */*f*/,
+                        int /*fd*/, unsigned /*mode*/,
+                        void (*/*func*/)(int /*fd*/,
+                                         unsigned /*mode*/,
+                                         void */*p*/),
+                        void */*p*/);
+
+/* --- @sel_addfile@ --- *
+ *
+ * Arguments:  @sel_file *f@ = pointer to a file block
+ *
+ * Returns:    ---
+ *
+ * Use:                Adds a file block into the list of things to listen to.
+ */
+
+extern void sel_addfile(sel_file */*f*/);
+
+/* --- @sel_rmfile@ --- *
+ *
+ * Arguments:  @sel_file *f@ = pointer to a file block
+ *
+ * Returns:    ---
+ *
+ * Use:                Removes a file block from the list of things to listen to.
+ */
+
+extern void sel_rmfile(sel_file */*f*/);
+
+/* --- @sel_force@ --- *
+ *
+ * Arguments:  @sel_file *f@ = pointer to file selector
+ *
+ * Returns:    ---
+ *
+ * Use:                Forces a file selector to be considered ready.  This is only
+ *             useful during a call to @sel_select@.  Of particular use is
+ *             forcing a write selector when there's something interesting
+ *             ready for it.
+ */
+
+extern void sel_force(sel_file */*f*/);
+
+/* --- @sel_addtimer@ --- *
+ *
+ * Arguments:  @sel_state *s@ = pointer to a state block
+ *             @sel_timer *t@ = pointer to a timer block
+ *             @struct timeval *tv@ = pointer to time to activate
+ *             @void *p@ = argument for handler function
+ *
+ * Returns:    ---
+ *
+ * Use:                Registers and sets up a timer.
+ */
+
+extern void sel_addtimer(sel_state */*s*/, sel_timer */*t*/,
+                        struct timeval */*tv*/,
+                        void (*/*func*/)(struct timeval */*tv*/,
+                                         void */*p*/),
+                        void */*p*/);
+
+/* --- @sel_rmtimer@ --- *
+ *
+ * Arguments:  @sel_timer *t@ = pointer to timer block
+ *
+ * Returns:    ---
+ *
+ * Use:                Removes a timer from the list of timers.
+ */
+
+extern void sel_rmtimer(sel_timer */*t*/);
+
+/* --- @sel_addhook@ --- *
+ *
+ * Arguments:  @sel_state *s@ = pointer to state block
+ *             @sel_hook *h@ = pointer to hook block
+ *             @sel_hookfn before, after@ = hook functions
+ *             @void *p@ = pointer argument to pass to hook functions
+ *
+ * Returns:    ---
+ *
+ * Use:                Registers hook functions to be called on each select call.
+ */
+
+extern void sel_addhook(sel_state */*s*/, sel_hook */*h*/,
+                       sel_hookfn /*before*/, sel_hookfn /*after*/,
+                       void */*p*/);
+
+/* --- @sel_rmhook@ --- *
+ *
+ * Arguments:  @sel_hook *h@ = pointer to hook block
+ *
+ * Returns:    ---
+ *
+ * Use:                Removes hook functions.
+ */
+
+extern void sel_rmhook(sel_hook */*h*/);
+
+/* --- @sel_fdmerge@ --- *
+ *
+ * Arguments:  @fd_set *dest@ = destination FD set
+ *             @fd_set *fd@ = pointer to set to merge
+ *             @int maxfd@ = highest numbered descriptor in @fd@ + 1
+ *
+ * Returns:    Actual highest numbered descriptor.
+ *
+ * Use:                Merges file descriptor sets, and returns an accurate @maxfd@
+ *             value.
+ */
+
+extern int sel_fdmerge(fd_set */*dest*/, fd_set */*fd*/, int /*maxfd*/);
+
+/* --- @sel_select@ --- *
+ *
+ * Arguments:  @sel_state *s@ = pointer to state block
+ *
+ * Returns:    Zero if all OK, -1 on error.
+ *
+ * Use:                Does a @select@ call (or equivalent @poll@).
+ */
+
+extern int sel_select(sel_state */*s*/);
+
+/*----- That's all, folks -------------------------------------------------*/
+
+#ifdef __cplusplus
+  }
+#endif
+
+#endif
diff --git a/sel/selbuf.3 b/sel/selbuf.3
new file mode 100644 (file)
index 0000000..cdf561b
--- /dev/null
@@ -0,0 +1,106 @@
+.\" -*-nroff-*-
+.TH selbuf 3 "23 May 1999" "Straylight/Edgeware" "mLib utilities library"
+.SH NAME
+selbuf \- line-buffering input selector
+.\" @selbuf_enable
+.\" @selbuf_disable
+.\" @selbuf_setsize
+.\" @selbuf_init
+.\" @selbuf_destroy
+.SH SYNOPSIS
+.nf
+.B "#include <mLib/selbuf.h>"
+
+.BI "void selbuf_enable(selbuf *" b );
+.BI "void selbuf_disable(selbuf *" b );
+.BI "void selbuf_setsize(selbuf *" b ", size_t " sz );
+.BI "void selbuf_init(selbuf *" b ", sel_state *" s ", int " fd ,
+.BI "                 lbuf_func *" func ", void *" p );
+.BI "void selbuf_destroy(selbuf *" b );
+.fi
+.SH DESCRIPTION
+The
+.B selbuf
+subsystem is a selector which integrates with the
+.BR sel (3)
+system for I/O multiplexing.  It reads entire text lines from a file
+descriptor and passes them to a caller-defined function.  It uses the
+line buffer described in
+.BR lbuf (3)
+to do its work: you should read about it in order to understand exactly
+what gets considered to be a line of text and what doesn't, and the
+exact rules about what your line handling function should and shouldn't
+do.
+.PP
+The data for a line selector is stored in an object of type
+.BR selbuf .
+This object must be allocated by the caller, and initialized using the
+.B selbuf_init
+function.  This requires a fair few arguments:
+.TP
+.BI "selbuf *" b
+Pointer to the
+.B selbuf
+object to initialize.
+.TP
+.BI "sel_state *" s
+Pointer to a multiplexor object (type
+.BR sel_state )
+to which this selector should be attached.  See
+.BR sel (3)
+for more details about multiplexors, and how this whole system works.
+.TP
+.BI "int " fd
+The file descriptor of the stream the selector should read from.
+.TP
+.BI "lbuf_func *" func
+The
+.I "line handler"
+function.  It is passed a pointer to each line read from the file (or
+null to indicate end-of-file), the length of the line, and an arbitrary
+pointer (the
+.I p
+argument to
+.B selbuf_init
+described below).  For full details, see
+.BR lbuf (3).
+.TP
+.BI "void *" p
+A pointer argument passed to
+.I func
+for each line read from the file.  Apart from this, the pointer is not
+used at all.
+.PP
+The
+.B selbuf
+selector is immediately active.  Subsequent calls to
+.B sel_select
+on the same multiplexor will cause any complete lines read from the file
+to be passed to your handling function.  This function can at any time
+call
+.B selbuf_disable
+to stop itself from being called any more.  The selector is then
+disengaged from the I/O multiplexor and won't do anything until
+.B selbuf_enable
+is called.  Note that
+.B selbuf_enable
+may well immediately start emitting complete lines of text which were
+queued up from the last I/O operation: it doesn't necessarily wait for
+the next
+.B sel_select
+call.
+.PP
+The line buffer has a finite amount of memory for reading strings.  The
+size of this buffer is set by calling
+.B selbuf_setsize
+with the requested size.  The default buffer size is 256 bytes.
+.PP
+When it's finished with, a line buffer selector must be destroyed by
+calling
+.BR selbuf_destroy .
+.SH "SEE ALSO"
+.BR lbuf (3),
+.BR sel (3),
+.BR mLib (3).
+.SH AUTHOR
+Mark Wooding, <mdw@distorted.org.uk>
diff --git a/sel/selbuf.c b/sel/selbuf.c
new file mode 100644 (file)
index 0000000..b572f4b
--- /dev/null
@@ -0,0 +1,169 @@
+/* -*-c-*-
+ *
+ * Line-buffering select handler
+ *
+ * (c) 1999 Straylight/Edgeware
+ */
+
+/*----- Licensing notice --------------------------------------------------*
+ *
+ * This file is part of the mLib utilities library.
+ *
+ * mLib is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * mLib is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with mLib; if not, write to the Free
+ * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+ * MA 02111-1307, USA.
+ */
+
+/*----- Header files ------------------------------------------------------*/
+
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <sys/types.h>
+#include <sys/time.h>
+#include <unistd.h>
+
+#include "lbuf.h"
+#include "sel.h"
+#include "selbuf.h"
+
+/*----- Main code ---------------------------------------------------------*/
+
+/* --- @selbuf_enable@ --- *
+ *
+ * Arguments:  @selbuf *b@ = pointer to buffer block
+ *
+ * Returns:    ---
+ *
+ * Use:                Enables a buffer for reading, and emits any queued lines
+ *             to the buffer's owner.
+ */
+
+void selbuf_enable(selbuf *b)
+{
+  if (!(b->b.f & LBUF_ENABLE)) {
+    b->b.f |= LBUF_ENABLE;
+    sel_addfile(&b->reader);
+    lbuf_flush(&b->b, 0, 0);
+  }
+}
+
+/* --- @selbuf_disable@ --- *
+ *
+ * Arguments:  @selbuf *b@ = pointer to a buffer block
+ *
+ * Returns:    ---
+ *
+ * Use:                Disables a buffer.  It won't be read from until it's
+ *             enabled again.
+ */
+
+void selbuf_disable(selbuf *b)
+{
+  if (b->b.f & LBUF_ENABLE) {
+    b->b.f &= ~LBUF_ENABLE;
+    sel_rmfile(&b->reader);
+  }
+}
+
+/* --- @selbuf_read@ --- *
+ *
+ * Arguments:  @int fd@ = file descriptor to read from
+ *             @int mode@ = what we can do to the file
+ *             @void *vp@ = pointer to buffer context
+ *
+ * Returns:    ---
+ *
+ * Use:                Acts on the result of a @select@ call.
+ */
+
+static void selbuf_read(int fd, unsigned mode, void *vp)
+{
+  selbuf *b = vp;
+  char *p;
+  size_t sz;
+  int n;
+
+  sz = lbuf_free(&b->b, &p);
+  n = read(fd, p, sz);
+  if (n == 0)
+    lbuf_close(&b->b);
+  else if (n > 0)
+    lbuf_flush(&b->b, p, n);
+  else switch (errno) {
+    case EINTR:
+    case EAGAIN:
+#if EAGAIN != EWOULDBLOCK
+    case EWOULDBLOCK:
+#endif
+      return;
+    default:
+      lbuf_close(&b->b);
+  }
+}
+
+/* --- @selbuf_setsize@ --- *
+ *
+ * Arguments:  @selbuf *b@ = pointer to buffer block
+ *             @size_t sz@ = size of buffer
+ *
+ * Returns:    ---
+ *
+ * Use:                Sets the size of the buffer used for reading lines.
+ */
+
+void selbuf_setsize(selbuf *b, size_t sz)
+{
+  lbuf_setsize(&b->b, sz);
+}
+
+/* --- @selbuf_init@ --- *
+ *
+ * Arguments:  @selbuf *b@ = pointer to buffer block
+ *             @sel_state *s@ = pointer to select state to attach to
+ *             @int fd@ = file descriptor to listen to
+ *             @lbuf_func *func@ = function to call
+ *             @void *p@ = argument for function
+ *
+ * Returns:    ---
+ *
+ * Use:                Initializes a buffer block.
+ */
+
+void selbuf_init(selbuf *b, sel_state *s, int fd, lbuf_func *func, void *p)
+{
+  lbuf_init(&b->b, func, p);
+  b->b.f &= ~LBUF_ENABLE;
+  sel_initfile(s, &b->reader, fd, SEL_READ, selbuf_read, b);
+  selbuf_enable(b);
+}
+
+/* --- @selbuf_destroy@ --- *
+ *
+ * Arguments:  @selbuf *b@ = pointer to buffer block
+ *
+ * Returns:    ---
+ *
+ * Use:                Deallocates a line buffer and frees any resources it owned.
+ */
+
+void selbuf_destroy(selbuf *b)
+{
+  selbuf_disable(b);
+  lbuf_destroy(&b->b);
+}
+
+/*----- That's all, folks -------------------------------------------------*/
diff --git a/sel/selbuf.h b/sel/selbuf.h
new file mode 100644 (file)
index 0000000..08dcfd1
--- /dev/null
@@ -0,0 +1,123 @@
+/* -*-c-*-
+ *
+ * Line-buffering select handler
+ *
+ * (c) 1999 Straylight/Edgeware
+ */
+
+/*----- Licensing notice --------------------------------------------------*
+ *
+ * This file is part of the mLib utilities library.
+ *
+ * mLib is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * mLib is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with mLib; if not, write to the Free
+ * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+ * MA 02111-1307, USA.
+ */
+
+#ifndef MLIB_SELBUF_H
+#define MLIB_SELBUF_H
+
+#ifdef __cplusplus
+  extern "C" {
+#endif
+
+/*----- Header files ------------------------------------------------------*/
+
+#ifndef MLIB_LBUF_H
+#  include "lbuf.h"
+#endif
+
+#ifndef MLIB_SEL_H
+#  include "sel.h"
+#endif
+
+/*----- Data structures ---------------------------------------------------*/
+
+typedef struct selbuf {
+  sel_file reader;                     /* File selection object */
+  lbuf b;                              /* Line buffering object */
+} selbuf;
+
+/*----- Functions provided ------------------------------------------------*/
+
+/* --- @selbuf_enable@ --- *
+ *
+ * Arguments:  @selbuf *b@ = pointer to buffer block
+ *
+ * Returns:    ---
+ *
+ * Use:                Enables a buffer for reading, and emits any queued lines
+ *             to the buffer's owner.
+ */
+
+extern void selbuf_enable(selbuf */*b*/);
+
+/* --- @selbuf_disable@ --- *
+ *
+ * Arguments:  @selbuf *b@ = pointer to a buffer block
+ *
+ * Returns:    ---
+ *
+ * Use:                Disables a buffer.  It won't be read from until it's
+ *             enabled again.
+ */
+
+extern void selbuf_disable(selbuf */*b*/);
+
+/* --- @selbuf_setsize@ --- *
+ *
+ * Arguments:  @selbuf *b@ = pointer to buffer block
+ *             @size_t sz@ = size of buffer
+ *
+ * Returns:    ---
+ *
+ * Use:                Sets the size of the buffer used for reading lines.
+ */
+
+extern void selbuf_setsize(selbuf */*b*/, size_t /*sz*/);
+
+/* --- @selbuf_init@ --- *
+ *
+ * Arguments:  @selbuf *b@ = pointer to buffer block
+ *             @sel_state *s@ = pointer to select state to attach to
+ *             @int fd@ = file descriptor to listen to
+ *             @lbuf_func *func@ = function to call
+ *             @void *p@ = argument for function
+ *
+ * Returns:    ---
+ *
+ * Use:                Initializes a buffer block.
+ */
+
+extern void selbuf_init(selbuf */*b*/, sel_state */*s*/, int /*fd*/,
+                       lbuf_func */*func*/, void */*p*/);
+
+/* --- @selbuf_destroy@ --- *
+ *
+ * Arguments:  @selbuf *b@ = pointer to buffer block
+ *
+ * Returns:    ---
+ *
+ * Use:                Deallocates a line buffer and frees any resources it owned.
+ */
+
+extern void selbuf_destroy(selbuf */*b*/);
+
+/*----- That's all, folks -------------------------------------------------*/
+
+#ifdef __cplusplus
+  }
+#endif
+
+#endif
diff --git a/sel/selpk.3 b/sel/selpk.3
new file mode 100644 (file)
index 0000000..f0a56ee
--- /dev/null
@@ -0,0 +1,106 @@
+.\" -*-nroff-*-
+.TH selpk 3 "23 May 1999" "Straylight/Edgeware" "mLib utilities library"
+.SH NAME
+selpk \- packet-buffering input selector
+.\" @selpk_enable
+.\" @selpk_disable
+.\" @selpk_want
+.\" @selpk_init
+.\" @selpk_destroy
+.SH SYNOPSIS
+.nf
+.B "#include <mLib/selpk.h>"
+
+.BI "void selpk_enable(selpk *" pk );
+.BI "void selpk_disable(selpk *" pk );
+.BI "void selpk_want(selpk *" pk ", size_t " sz );
+.BI "void selpk_init(selpk *" pk ", sel_state *" s ", int " fd ,
+.BI "                pkbuf_func *" func ", void *" p );
+.BI "void selpk_destroy(selpk *" b );
+.fi
+.SH DESCRIPTION
+The
+.B selpk
+subsystem is a selector which integrates with the
+.BR sel (3)
+system for I/O multiplexing.  It reads packets from a file descriptor
+and passes them to a caller-defined function.  It uses the packet buffer
+described in
+.BR pkbuf (3)
+to do its work: you should read about it in order to understand exactly
+how the packet buffer decides how much data is in each packet and the
+exact rules about what your packet handling function should and
+shouldn't do.
+.PP
+The data for a packet selector is stored in an object of type
+.BR selpk .
+This object must be allocated by the caller, and initialized using the
+.B selpk_init
+function.  This requires a fair few arguments:
+.TP
+.BI "selpk *" pk
+Pointer to the
+.B selpk
+object to initialize.
+.TP
+.BI "sel_state *" s
+Pointer to a multiplexor object (type
+.BR sel_state )
+to which this selector should be attached.  See
+.BR sel (3)
+for more details about multiplexors, and how this whole system works.
+.TP
+.BI "int " fd
+The file descriptor of the stream the selector should read from.
+.TP
+.BI "pkbuf_func *" func
+The
+.I "packet handler"
+function.  It is given a pointer to each packet read from the file (or
+null to indicate end-of-file) and an arbitrary pointer (the
+.I p
+argument to
+.B selpk_init
+described below).  See
+.BR pkbuf (3)
+for full details.
+.TP
+.BI "void *" p
+A pointer argument passed to
+.I func
+for each packet read from the file.  Apart from this, the pointer is not
+used at all.
+.PP
+The
+.B selpk
+selector is immediately active.  Subsequent calls to
+.B sel_select
+on the same multiplexor will cause any packets read from the file to be
+passed to your handling function.  This function can at any time call
+.B selpk_disable
+to stop itself from being called any more.  The selector is then
+disengaged from the I/O multiplexor and won't do anything until
+.B selpk_enable
+is called.  Note that
+.B selpk_enable
+may well immediately start emitting complete packets of text which were
+queued up from the last I/O operation: it doesn't necessarily wait for
+the next
+.B sel_select
+call.
+.PP
+The size of packets read by the buffer is set by calling
+.BR selpk_want .
+See
+.BR pkbuf (3)
+for more details about how packet buffering works.
+.PP
+When it's finished with, a packet selector must be destroyed by calling
+.BR selpk_destroy .
+.SH "SEE ALSO"
+.BR pkbuf (3),
+.BR sel (3),
+.BR selbuf (3),
+.BR mLib (3).
+.SH AUTHOR
+Mark Wooding, <mdw@distorted.org.uk>
diff --git a/sel/selpk.c b/sel/selpk.c
new file mode 100644 (file)
index 0000000..450e08b
--- /dev/null
@@ -0,0 +1,169 @@
+/* -*-c-*-
+ *
+ * Packet-buffering select handler
+ *
+ * (c) 1999 Straylight/Edgeware
+ */
+
+/*----- Licensing notice --------------------------------------------------*
+ *
+ * This file is part of the mLib utilities library.
+ *
+ * mLib is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * mLib is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with mLib; if not, write to the Free
+ * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+ * MA 02111-1307, USA.
+ */
+
+/*----- Header files ------------------------------------------------------*/
+
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <sys/types.h>
+#include <sys/time.h>
+#include <unistd.h>
+
+#include "pkbuf.h"
+#include "sel.h"
+#include "selpk.h"
+
+/*----- Main code ---------------------------------------------------------*/
+
+/* --- @selpk_enable@ --- *
+ *
+ * Arguments:  @selpk *pk@ = pointer to buffer block
+ *
+ * Returns:    ---
+ *
+ * Use:                Enables a buffer for reading, and emits any queued packets
+ *             to the buffer's owner.
+ */
+
+void selpk_enable(selpk *pk)
+{
+  if (!(pk->pk.f & PKBUF_ENABLE)) {
+    pk->pk.f |= PKBUF_ENABLE;
+    sel_addfile(&pk->reader);
+    pkbuf_flush(&pk->pk, 0, 0);
+  }
+}
+
+/* --- @selpk_disable@ --- *
+ *
+ * Arguments:  @selpk *pk@ = pointer to a buffer block
+ *
+ * Returns:    ---
+ *
+ * Use:                Disables a buffer.  It won't be read from until it's
+ *             enabled again.
+ */
+
+void selpk_disable(selpk *pk)
+{
+  if (pk->pk.f & PKBUF_ENABLE) {
+    pk->pk.f &= ~PKBUF_ENABLE;
+    sel_rmfile(&pk->reader);
+  }
+}
+
+/* --- @selpk_read@ --- *
+ *
+ * Arguments:  @int fd@ = file descriptor to read from
+ *             @int mode@ = what we can do to the file
+ *             @void *vp@ = pointer to buffer context
+ *
+ * Returns:    ---
+ *
+ * Use:                Acts on the result of a @select@ call.
+ */
+
+static void selpk_read(int fd, unsigned mode, void *vp)
+{
+  selpk *pk = vp;
+  octet *p;
+  size_t sz;
+  int n;
+
+  sz = pkbuf_free(&pk->pk, &p);
+  n = read(fd, p, sz);
+  if (n == 0)
+    pkbuf_close(&pk->pk);
+  else if (n > 0)
+    pkbuf_flush(&pk->pk, p, n);
+  else switch (errno) {
+    case EINTR:
+    case EAGAIN:
+#if EAGAIN != EWOULDBLOCK
+    case EWOULDBLOCK:
+#endif
+      return;
+    default:
+      pkbuf_close(&pk->pk);
+  }
+}
+
+/* --- @selpk_want@ --- *
+ *
+ * Arguments:  @selpk *pk@ = pointer to buffer block
+ *             @size_t sz@ = size of buffer
+ *
+ * Returns:    ---
+ *
+ * Use:                Sets the size of the packet to be read next.
+ */
+
+void selpk_want(selpk *pk, size_t sz)
+{
+  pkbuf_want(&pk->pk, sz);
+}
+
+/* --- @selpk_init@ --- *
+ *
+ * Arguments:  @selpk *pk@ = pointer to buffer block
+ *             @sel_state *s@ = pointer to select state to attach to
+ *             @int fd@ = file descriptor to listen to
+ *             @pkbuf_func *func@ = function to call
+ *             @void *p@ = argument for function
+ *
+ * Returns:    ---
+ *
+ * Use:                Initializes a buffer block.
+ */
+
+void selpk_init(selpk *pk, sel_state *s, int fd, pkbuf_func *func, void *p)
+{
+  pkbuf_init(&pk->pk, func, p);
+  pk->pk.f &= ~PKBUF_ENABLE;
+  sel_initfile(s, &pk->reader, fd, SEL_READ, selpk_read, pk);
+  selpk_enable(pk);
+}
+
+/* --- @selpk_destroy@ --- *
+ *
+ * Arguments:  @selpk *pk@ = pointer to buffer block
+ *
+ * Returns:    ---
+ *
+ * Use:                Deallocates a packet buffer and frees any resources it owned.
+ */
+
+void selpk_destroy(selpk *pk)
+{
+  selpk_disable(pk);
+  pkbuf_destroy(&pk->pk);
+}
+
+/*----- That's all, folks -------------------------------------------------*/
diff --git a/sel/selpk.h b/sel/selpk.h
new file mode 100644 (file)
index 0000000..c43d8ad
--- /dev/null
@@ -0,0 +1,123 @@
+/* -*-c-*-
+ *
+ * Packet-buffering select handler
+ *
+ * (c) 1999 Straylight/Edgeware
+ */
+
+/*----- Licensing notice --------------------------------------------------*
+ *
+ * This file is part of the mLib utilities library.
+ *
+ * mLib is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * mLib is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with mLib; if not, write to the Free
+ * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+ * MA 02111-1307, USA.
+ */
+
+#ifndef MLIB_SELPK_H
+#define MLIB_SELPK_H
+
+#ifdef __cplusplus
+  extern "C" {
+#endif
+
+/*----- Header files ------------------------------------------------------*/
+
+#ifndef MLIB_PKBUF_H
+#  include "pkbuf.h"
+#endif
+
+#ifndef MLIB_SEL_H
+#  include "sel.h"
+#endif
+
+/*----- Data structures ---------------------------------------------------*/
+
+typedef struct selpk {
+  sel_file reader;                     /* File selection object */
+  pkbuf pk;                            /* Packet buffering object */
+} selpk;
+
+/*----- Functions provided ------------------------------------------------*/
+
+/* --- @selpk_enable@ --- *
+ *
+ * Arguments:  @selpk *pk@ = pointer to buffer block
+ *
+ * Returns:    ---
+ *
+ * Use:                Enables a buffer for reading, and emits any queued packets
+ *             to the buffer's owner.
+ */
+
+extern void selpk_enable(selpk */*pk*/);
+
+/* --- @selpk_disable@ --- *
+ *
+ * Arguments:  @selpk *pk@ = pointer to a buffer block
+ *
+ * Returns:    ---
+ *
+ * Use:                Disables a buffer.  It won't be read from until it's
+ *             enabled again.
+ */
+
+extern void selpk_disable(selpk */*pk*/);
+
+/* --- @selpk_want@ --- *
+ *
+ * Arguments:  @selpk *pk@ = pointer to buffer block
+ *             @size_t want@ = size of packet to read
+ *
+ * Returns:    ---
+ *
+ * Use:                Sets the size of the packet to be read next.
+ */
+
+extern void selpk_want(selpk */*pk*/, size_t /*sz*/);
+
+/* --- @selpk_init@ --- *
+ *
+ * Arguments:  @selpk *pk@ = pointer to buffer block
+ *             @sel_state *s@ = pointer to select state to attach to
+ *             @int fd@ = file descriptor to listen to
+ *             @pkbuf_func *func@ = function to call
+ *             @void *p@ = argument for function
+ *
+ * Returns:    ---
+ *
+ * Use:                Initializes a buffer block.
+ */
+
+extern void selpk_init(selpk */*pk*/, sel_state */*s*/, int /*fd*/,
+                      pkbuf_func */*func*/, void */*p*/);
+
+/* --- @selpk_destroy@ --- *
+ *
+ * Arguments:  @lbuf *b@ = pointer to buffer block
+ *
+ * Returns:    ---
+ *
+ * Use:                Deallocates a packet buffer and frees any resources it owned.
+ */
+
+extern void selpk_destroy(selpk */*pk*/);
+
+/*----- That's all, folks -------------------------------------------------*/
+
+#ifdef __cplusplus
+  }
+#endif
+
+#endif
diff --git a/sel/sig.3 b/sel/sig.3
new file mode 100644 (file)
index 0000000..a176e12
--- /dev/null
+++ b/sel/sig.3
@@ -0,0 +1,93 @@
+.\" -*-nroff-*-
+.TH sel 3 "23 July 1999" "Straylight/Edgeware" "mLib utilities library"
+.SH NAME
+sig \- more controlled signal handling
+.\" @sig_init
+.\" @sig_add
+.\" @sig_remove
+.SH SYNOPSIS
+.nf
+.B "#include <mLib/sig.h>"
+
+.BI "void sig_add(sig *" s ", int " n ,
+.BI "             void (*" proc ")(int " n ", void *" p "), void *" p );
+.BI "void sig_remove(sig *" s );
+.BI "void sig_init(sel_state *" s );
+.fi
+.SH "DESCRIPTION"
+The
+.B sig
+subsystem uses the I/O multiplexing capabilities of
+.B mLib
+(see
+.BR sel (3)
+for details) to provide a more convenient interface for handling signals
+which don't need to be dealt with `right away'.  Like the I/O system,
+.B sig
+doesn't allocate any memory for itself: you have to give it space to
+work in.
+.PP
+The system needs to be initialized before use.  To do this, you must
+call
+.BR sig_init ,
+passing it the address of an initialized multiplexor object.  Signals
+handled through this interface will only be delivered when
+.BR sel_select (3)
+is called on that multiplexor.
+.PP
+To register interest in a signal, call
+.BR sig_add ,
+passing it the following arguments:
+.TP
+.BI "sig *" s
+A pointer to an (uninitialized) object of type
+.BR sig .
+This will be used by the system to retain information about this signal
+claim.  You use the address of this object to remove the handler again
+when you've finished.
+.TP
+.BI "int " n
+The number of the signal you want to handle.
+.PP
+.TP
+.BI "void (*" proc ")(int " n ", void *" p )
+A function to call when the signal is detected.  The function is passed
+the signal number and the pointer
+.I p
+passed to
+.BR sig_add .
+.TP
+.BI "void *" p
+A pointer argument to be passed to
+.I func
+when the signal is detected.
+.PP
+Removing a handler is easy.  Call
+.B sig_remove
+with the address of the
+.B sig
+structure you passed to
+.BR sig_add .
+.SS "Multiple signal handlers"
+You may have multiple signal handlers for a signal.  All of them are
+called in some unspecified order when the signal occurs.
+.PP
+A signal's disposition is remembered when a handler for it is added and
+there are no handlers already registered.  When the last handler for a
+signal is removed, its disposition is restored to its initial remembered
+state.
+.SH "BUGS AND CAVEATS"
+The
+.B sig
+system attempts to set the
+.B SA_RESTART
+flag on signal handlers it creates that signal occurrences don't
+interrupt system calls.  This won't be done on systems which don't
+define this flag, for obvious reasons.
+.PP
+The
+.B SA_NOCLDSTOP
+flag is also set, so that stopped child processes aren't reported by a
+signal.  This is normally right, but ought to be configurable.
+.SH "AUTHOR"
+Mark Wooding, <mdw@distorted.org.uk>
diff --git a/sel/sig.c b/sel/sig.c
new file mode 100644 (file)
index 0000000..0160bf1
--- /dev/null
+++ b/sel/sig.c
@@ -0,0 +1,288 @@
+/* -*-c-*-
+ *
+ * Signal handling
+ *
+ * (c) 1999 Straylight/Edgeware
+ */
+
+/*----- Licensing notice --------------------------------------------------*
+ *
+ * This file is part of the mLib utilities library.
+ *
+ * mLib is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * mLib is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with mLib; if not, write to the Free
+ * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+ * MA 02111-1307, USA.
+ */
+
+/*----- Header files ------------------------------------------------------*/
+
+#include <errno.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <fcntl.h>
+
+#include "alloc.h"
+#include "fdflags.h"
+#include "macros.h"
+#include "report.h"
+#include "sel.h"
+#include "sig.h"
+
+/*----- Data structures ---------------------------------------------------*/
+
+typedef struct sigstate {
+  struct sigaction sa;
+  sig *list;
+} sigstate;
+
+/*----- Static variables --------------------------------------------------*/
+
+static sel_file sigsel;
+static int sigfd;
+static sigstate *sigs;
+static sigset_t ss_all, ss_caught;
+static unsigned nsig;
+
+/*----- Main code ---------------------------------------------------------*/
+
+/* --- @sig_handler@ --- *
+ *
+ * Arguments:  @int n@ = signal number
+ *
+ * Returns:    ---
+ *
+ * Use:                Generic signal handler.  Writes a single byte to the
+ *             signal pipe.  The byte contains the signal number.  Also
+ *             sets the appropriate bit in @ss_caught@ to indicate which
+ *             signals are pending.
+ */
+
+static void sig_handler(int n)
+{
+  int e = errno;
+  unsigned char sch = (unsigned char)n;
+  sigprocmask(SIG_BLOCK, &ss_all, 0);
+  sigaddset(&ss_caught, n);
+  DISCARD(write(sigfd, &sch, 1));
+  /* The system should reset the signal mask here. */
+  errno = e;
+}
+
+/* --- @sig_read@ --- *
+ *
+ * Arguments:  @int fd@ = file descriptor to read
+ *             @unsigned mode@ = thing to do with descriptor
+ *             @void *p@ = uninteresting argument
+ *
+ * Returns:    ---
+ *
+ * Use:                Dispatches signals to their handlers safely.
+ */
+
+static void sig_read(int fd, unsigned mode, void *p)
+{
+  sigset_t ss;
+
+  /* --- Read the currently caught signals --- *
+   *
+   * Block signals while the mask is being copied.
+   */
+
+  {
+    sigset_t oss;
+    unsigned char buf[256];
+
+    sigprocmask(SIG_BLOCK, &ss_all, &oss);
+    ss = ss_caught;
+    sigemptyset(&ss_caught);
+    while (read(fd, buf, sizeof(buf)) > 0)
+      /* Do nothing */;
+    sigprocmask(SIG_SETMASK, &oss, 0);
+  }
+
+  /* --- Process the caught signals --- */
+
+  {
+    int i;
+    for (i = 0; i < nsig; i++) {
+      sig *s;
+      if (!sigismember(&ss, i))
+       continue;
+      s = sigs[i].list;
+      while (s) {
+       sig *ss = s;
+       s = s->next;
+       ss->proc(i, ss->p);
+      }
+    }
+  }
+}
+
+/* --- @sig_add@ --- *
+ *
+ * Arguments:  @sig *s@ = pointer to signal handler block
+ *             @int n@ = number of the signal
+ *             @void (*proc)(int n, void *p)@ = signal handler function
+ *             @void *p@ = argument to pass to handler
+ *
+ * Returns:    ---
+ *
+ * Use:                Adds a signal handler.
+ */
+
+void sig_add(sig *s, int n, void (*proc)(int /*n*/, void */*p*/), void *p)
+{
+  sigstate *ss = &sigs[n];
+
+  /* --- Initialize the block --- */
+
+  s->proc = proc;
+  s->p = p;
+  s->sig = n;
+  s->next = ss->list;
+  s->prev = 0;
+
+  /* --- Engage a signal handler, maybe --- */
+
+  if (!ss->list) {
+    struct sigaction sa;
+    sa.sa_handler = sig_handler;
+    sa.sa_flags = SA_NOCLDSTOP;
+#ifdef SA_RESTART
+    sa.sa_flags |= SA_RESTART;
+#endif
+    sigemptyset(&sa.sa_mask);
+    sigaddset(&ss_all, n);
+    sigaction(n, &sa, &ss->sa);
+  }
+
+  /* --- Link the block into the list --- */
+
+  if (ss->list)
+    ss->list->prev = s;
+  ss->list = s;
+}
+
+/* --- @sig_remove@ --- *
+ *
+ * Arguments:  @sig *s@ = pointer to signal handler block
+ *
+ * Returns:    ---
+ *
+ * Use:                Removes the signal handler from the list.
+ */
+
+void sig_remove(sig *s)
+{
+  sigstate *ss = &sigs[s->sig];
+
+  /* --- Unlink the handler block --- */
+
+  if (s->next)
+    s->next->prev = s->prev;
+  if (s->prev)
+    s->prev->next = s->next;
+  else
+    ss->list = s->next;
+
+  /* --- Maybe remove the handler --- */
+
+  if (!ss->list) {
+    sigaction(s->sig, &ss->sa, 0);
+    sigdelset(&ss_all, s->sig);
+  }
+}
+
+/* --- @sig_init@ --- *
+ *
+ * Arguments:  @sel_state *s@ = pointer to select state
+ *
+ * Returns:    ---
+ *
+ * Use:                Initializes the signal handling system ready for use.
+ */
+
+void sig_init(sel_state *s)
+{
+  int fd[2];
+
+  /* --- Work out how many signals there are --- */
+
+  {
+    sigset_t ss;
+    unsigned min = 0, max = 0;
+
+    sigemptyset(&ss);
+
+    /* --- Get a good upper bound --- *
+     *
+     * Cap the search at 256.  I'll be sending signal numbers as bytes.
+     */
+
+    nsig = 1;
+    while (sigaddset(&ss, nsig) == 0) {
+      if (nsig == 256)
+       goto counted;
+      nsig <<= 1;
+    }
+
+    /* --- Now binary search until I find the actual limit --- */
+
+    min = nsig >> 1;
+    max = nsig;
+    for (;;) {
+      nsig = (min + max) >> 1;
+      if (nsig == min)
+       break;
+      if (sigaddset(&ss, nsig))
+       max = nsig;
+      else
+       min = nsig;
+    }
+  counted:;
+  }
+
+  /* --- Initialize the signal state table --- */
+
+  {
+    unsigned i;
+
+    sigs = xmalloc(nsig * sizeof(*sigs));
+    for (i = 0; i < nsig; i++)
+      sigs[i].list = 0;
+  }
+
+  /* --- Create the signal pipe --- */
+
+  if (pipe(fd))
+    die(1, "couldn't create pipe for signal handling");
+
+  /* --- Set both ends to nonblocking and close-on-exec --- */
+
+  fdflags(fd[0], O_NONBLOCK, O_NONBLOCK, FD_CLOEXEC, FD_CLOEXEC);
+  fdflags(fd[1], O_NONBLOCK, O_NONBLOCK, FD_CLOEXEC, FD_CLOEXEC);
+
+  /* --- Set everything up for the future --- */
+
+  sigfd = fd[1];
+  sel_initfile(s, &sigsel, fd[0], SEL_READ, sig_read, 0);
+  sel_addfile(&sigsel);
+  sigemptyset(&ss_all);
+  sigemptyset(&ss_caught);
+}
+
+/*----- That's all, folks -------------------------------------------------*/
diff --git a/sel/sig.h b/sel/sig.h
new file mode 100644 (file)
index 0000000..02baef6
--- /dev/null
+++ b/sel/sig.h
@@ -0,0 +1,98 @@
+/* -*-c-*-
+ *
+ * Signal handling
+ *
+ * (c) 1999 Straylight/Edgeware
+ */
+
+/*----- Licensing notice --------------------------------------------------*
+ *
+ * This file is part of the mLib utilities library.
+ *
+ * mLib is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * mLib is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with mLib; if not, write to the Free
+ * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+ * MA 02111-1307, USA.
+ */
+
+#ifndef MLIB_SIG_H
+#define MLIB_SIG_H
+
+#ifdef __cplusplus
+  extern "C" {
+#endif
+
+/*----- Header files ------------------------------------------------------*/
+
+#include <signal.h>
+
+#ifndef MLIB_SEL_H
+#  include "sel.h"
+#endif
+
+/*----- Data structures ---------------------------------------------------*/
+
+typedef struct sig {
+  struct sig *next;
+  struct sig *prev;
+  int sig;
+  void (*proc)(int /*n*/, void */*p*/);
+  void *p;
+} sig;
+
+/*----- Functions provided ------------------------------------------------*/
+
+/* --- @sig_add@ --- *
+ *
+ * Arguments:  @sig *s@ = pointer to signal handler block
+ *             @int n@ = number of the signal
+ *             @void (*proc)(int n, void *p)@ = signal handler function
+ *             @void *p@ = argument to pass to handler
+ *
+ * Returns:    ---
+ *
+ * Use:                Adds a signal handler.
+ */
+
+extern void sig_add(sig */*s*/, int /*n*/,
+                   void (*/*proc*/)(int /*n*/, void */*p*/), void */*p*/);
+
+/* --- @sig_remove@ --- *
+ *
+ * Arguments:  @sig *s@ = pointer to signal handler block
+ *
+ * Returns:    ---
+ *
+ * Use:                Removes the signal handler from the list.
+ */
+
+extern void sig_remove(sig */*s*/);
+
+/* --- @sig_init@ --- *
+ *
+ * Arguments:  @sel_state *s@ = pointer to select state
+ *
+ * Returns:    ---
+ *
+ * Use:                Initializes the signal handling system ready for use.
+ */
+
+extern void sig_init(sel_state */*s*/);
+
+/*----- That's all, folks -------------------------------------------------*/
+
+#ifdef __cplusplus
+  }
+#endif
+
+#endif
diff --git a/struct/Makefile.am b/struct/Makefile.am
new file mode 100644 (file)
index 0000000..6365023
--- /dev/null
@@ -0,0 +1,93 @@
+### -*-makefile-*-
+###
+### Build script for data structures
+###
+### (c) 2009 Straylight/Edgeware
+###
+
+###----- Licensing notice ---------------------------------------------------
+###
+### This file is part of the mLib utilities library.
+###
+### mLib is free software; you can redistribute it and/or modify
+### it under the terms of the GNU Library General Public License as
+### published by the Free Software Foundation; either version 2 of the
+### License, or (at your option) any later version.
+###
+### mLib is distributed in the hope that it will be useful,
+### but WITHOUT ANY WARRANTY; without even the implied warranty of
+### MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+### GNU Library General Public License for more details.
+###
+### You should have received a copy of the GNU Library General Public
+### License along with mLib; if not, write to the Free
+### Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+### MA 02111-1307, USA.
+
+include $(top_srcdir)/vars.am
+
+noinst_LTLIBRARIES      = libstruct.la
+libstruct_la_SOURCES    =
+
+###--------------------------------------------------------------------------
+### Component files.
+
+## Dynamic strings.
+pkginclude_HEADERS     += dstr.h dspool.h
+libstruct_la_SOURCES   += dstr.c dstr-putf.c dspool.c
+LIBMANS                        += dstr.3 dspool.3
+
+check_PROGRAMS         += t/dstr-putf.t
+t_dstr_putf_t_SOURCES   = t/dstr-putf-test.c
+t_dstr_putf_t_LDFLAGS   = -static
+
+## Buffers.
+pkginclude_HEADERS     += buf.h
+libstruct_la_SOURCES   += buf.c buf-dstr.c
+LIBMANS                        += buf.3
+
+## Dynamic arrays.
+pkginclude_HEADERS     += darray.h
+libstruct_la_SOURCES   += darray.c
+LIBMANS                        += darray.3
+
+check_PROGRAMS         += t/darray.t
+t_darray_t_SOURCES      = t/da-test.c
+t_darray_t_CPPFLAGS     = $(TEST_CPPFLAGS)
+t_darray_t_LDFLAGS      = -static
+
+EXTRA_DIST             += t/da-gtest.py
+
+## Hash tables.
+pkginclude_HEADERS     += hash.h
+libstruct_la_SOURCES   += hash.c
+LIBMANS                        += hash.3
+
+## Symbol tables.
+pkginclude_HEADERS     += sym.h
+libstruct_la_SOURCES   += sym.c
+LIBMANS                        += sym.3
+
+check_PROGRAMS         += t/sym.t
+t_sym_t_SOURCES                 = t/sym-test.c
+t_sym_t_CPPFLAGS        = $(TEST_CPPFLAGS)
+t_sym_t_LDFLAGS                 = -static
+
+EXTRA_DIST             += t/sym-gtest.py
+
+## Atoms.
+pkginclude_HEADERS     += atom.h
+libstruct_la_SOURCES   += atom.c
+LIBMANS                        += atom.3
+
+## Association tables.
+pkginclude_HEADERS     += assoc.h
+libstruct_la_SOURCES   += assoc.c
+LIBMANS                        += assoc.3
+
+check_PROGRAMS         += t/assoc.t
+t_assoc_t_SOURCES       = t/assoc-test.c
+t_assoc_t_CPPFLAGS      = $(TEST_CPPFLAGS)
+t_assoc_t_LDFLAGS       = -static
+
+###----- That's all, folks --------------------------------------------------
diff --git a/struct/assoc.3 b/struct/assoc.3
new file mode 100644 (file)
index 0000000..5b4ead0
--- /dev/null
@@ -0,0 +1,155 @@
+.\" -*-nroff-*-
+.de VS
+.sp 1
+.RS
+.nf
+.ft B
+..
+.de VE
+.ft R
+.fi
+.RE
+.sp 1
+..
+.TH assoc 3 "23 January 2001" "Straylight/Edgeware" "mLib utilities library"
+.SH NAME
+assoc \- tables indexed by atoms
+.\" @assoc_create
+.\" @assoc_destroy
+.\" @assoc_find
+.\" @assoc_remove
+.\" @assoc_mkiter
+.\" @assoc_next
+.\"
+.\" @ASSOC_ATOM
+.\"
+.SH SYNOPSIS
+.nf
+.B "#include <mLib/assoc.h>"
+
+.BI "void assoc_create(assoc_table *" t );
+.BI "void assoc_destroy(assoc_table *" t );
+
+.BI "void *assoc_find(assoc_table *" t ", atom *" a ", size_t " sz ", unsigned *" f );
+.BI "void assoc_remove(assoc_table *" t ", void *" b );
+
+.BI "atom *ASSOC_ATOM(const void *" p );
+
+.BI "void assoc_mkiter(assoc_iter *" i ", assoc_table *" t );
+.BI "void *assoc_next(assoc_iter *" i );
+.fi
+.SH DESCRIPTION
+An
+.I "association table"
+is a data structure which maps atoms (see
+.BR atom (3))
+to arbitrary values.  It works in a similar way to the symbol tables
+implemented by
+.BR sym (3),
+except that it uses atoms rather than blocks of data as keys.
+.PP
+Like
+.BR sym (3),
+it implements an
+.I intrusive
+table: the value structures must include an
+.B assoc_base
+structure.
+.PP
+There are three main data structures:
+.TP
+.B assoc_table
+Keeps track of the information associated with a particular table.
+.TP
+.B assoc_base
+The header which must be attached to the front of all the value objects.
+.TP
+.B assoc_iter
+An iterator object, used for enumerating all of the associations stored
+in the table
+.PP
+All of the above structures should be considered
+.IR opaque :
+don't try looking inside.
+.SS "Creation and destruction"
+The
+.B assoc_table
+object itself needs to be allocated by the caller.  It is initialized by
+passing it to the function
+.BR assoc_create .
+After initialization, the table contains no entries.
+.PP
+Initializing an association table involves allocating some memory.  If
+this allocation fails, an
+.B EXC_NOMEM
+exception is raised.
+.PP
+When an association table is no longer needed, the memory occupied by
+the values and other maintenance structures can be reclaimed by calling
+.BR assoc_destroy .
+Any bits of user data attached to values should previously have been
+destroyed.
+.SS "Adding, searching and removing"
+Most of the actual work is done by the function
+.BR assoc_find .
+It does both lookup and creation, depending on its arguments.  To do its
+job, it needs to know the following bits of information:
+.TP
+.BI "assoc_table *" t
+A pointer to an association table to manipulate.
+.TP
+.BI "atom *" a
+The address of the atom to use as a key.
+.TP
+.BI "size_t " sz
+The size of the value block to allocate if the key could not be found.
+If this is zero, no value is allocated, and a null pointer is returned
+to indicate an unsuccessful lookup.
+.TP
+.BI "unsigned *" f
+The address of a `found' flag to set.  This is an output parameter.  On
+exit,
+.B assoc_find
+will set the value of
+.BI * f
+to zero if the key could not be found, or nonzero if it was found.  This
+can be used to tell whether the value returned has been newly allocated,
+or whether it was already in the table.
+.PP
+A symbol can be removed from the table by calling
+.BR assoc_remove ,
+passing the association table itself, and the value block that needs
+removing.
+.SS "Enquiries about associations"
+Given a pointer
+.I a
+to an association, the expression
+.BI ASSOC_ATOM( a )
+has as its value a poinetr to the atom which is that association's key.
+.SS "Enumerating associations"
+Enumerating the values in an association table is fairly simple.
+Allocate an
+.B assoc_iter
+object from somewhere.  Attach it to an association table by calling
+.BR assoc_mkiter ,
+and passing in the addresses of the iterator and the symbol table.
+Then, each call to
+.B assoc_next
+will return a different value from the association table, until all of
+them have been enumerated, at which point,
+.B assoc_next
+returns a null pointer.
+.PP
+It's safe to remove the symbol you've just been returned by
+.BR assoc_next .
+However, it's not safe to remove any other symbol.  So don't do that.
+.PP
+When you've finished with an iterator, it's safe to just throw it away.
+You don't need to call any functions beforehand.
+.SH SEE ALSO
+.BR atom (3),
+.BR hash (3),
+.BR sym (3),
+.BR mLib (3).
+.SH AUTHOR
+Mark Wooding, <mdw@distorted.org.uk>
diff --git a/struct/assoc.c b/struct/assoc.c
new file mode 100644 (file)
index 0000000..27d25bf
--- /dev/null
@@ -0,0 +1,168 @@
+/* -*-c-*-
+ *
+ * Assocation tables
+ *
+ * (c) 2000 Straylight/Edgeware
+ */
+
+/*----- Licensing notice --------------------------------------------------*
+ *
+ * This file is part of the mLib utilities library.
+ *
+ * mLib is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * mLib is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with mLib; if not, write to the Free
+ * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+ * MA 02111-1307, USA.
+ */
+
+/*----- Header files ------------------------------------------------------*/
+
+#include "alloc.h"
+#include "assoc.h"
+#include "atom.h"
+#include "hash.h"
+
+/*----- Main code ---------------------------------------------------------*/
+
+/* --- @assoc_create@ --- *
+ *
+ * Arguments:  @assoc_table *t@ = pointer to an association table
+ *
+ * Returns:    ---
+ *
+ * Use:                Creates a new association table.
+ */
+
+void assoc_create(assoc_table *t)
+{
+  hash_create(&t->t, SYM_INITSZ);
+  t->load = SYM_LIMIT(SYM_INITSZ);
+}
+
+/* --- @assoc_destroy@ --- *
+ *
+ * Arguments:  @assoc_table *t@ = pointer to an association table
+ *
+ * Returns:    ---
+ *
+ * Use:                Destroys an association table.
+ */
+
+void assoc_destroy(assoc_table *t)
+{
+  hash_iter i;
+
+  HASH_MKITER(&i, &t->t);
+  for (;;) {
+    hash_base *p;
+    HASH_NEXT(&i, p);
+    if (!p)
+      break;
+    x_free(t->t.a, p);
+  }
+  hash_destroy(&t->t);
+}
+
+/* --- @assoc_find@ --- *
+ *
+ * Arguments:  @assoc_table *t@ = pointer to an association table
+ *             @atom *a@ = an atom to label the item
+ *             @size_t sz@ = size of block to allocate
+ *             @unsigned *f@ = pointer to `found' flag
+ *
+ * Returns:    A pointer to the item located or null.
+ *
+ * Use:                Looks up an atom in an association table.  If the atom is
+ *             found, the association is returned.  If not, and @sz@ is
+ *             zero, a null pointer is returned.  Otherwise, a block of size
+ *             @sz@ is allocated, its @assoc_base@ header is filled in, and
+ *             the pointer returned.  The flag @*f@ is cleared if the item
+ *             couldn't be found, or set if it was.
+ *
+ *             All the atoms used in a particular table should come from the
+ *             same atom table.
+ */
+
+void *assoc_find(assoc_table *t, atom *a, size_t sz, unsigned *f)
+{
+  hash_base **bin = HASH_BIN(&t->t, ATOM_HASH(a)), **p;
+  assoc_base *q;
+
+  /* --- Try to find the association --- */
+
+  for (p = bin; *p; p = &(*p)->next) {
+    q = (assoc_base *)*p;
+    if (q->a == a) {
+      *p = q->b.next;
+      q->b.next = *bin;
+      *bin = &q->b;
+      if (f) *f = 1;
+      return (q);
+    }
+  }
+
+  /* --- Failed to find a match --- */
+
+  if (f) *f = 0;
+  if (!sz) return (0);
+
+  /* --- Make a new association --- */
+
+  q = x_alloc(t->t.a, sz);
+  q->a = a;
+  q->b.next = *bin;
+  q->b.hash = ATOM_HASH(a);
+  *bin = &q->b;
+
+  /* --- Maybe extend the table --- */
+
+  if (t->load)
+    t->load--;
+  if (!t->load && hash_extend(&t->t))
+    t->load = SYM_LIMIT(t->t.mask + 1);
+  return (q);
+}
+
+/* --- @assoc_remove@ --- *
+ *
+ * Arguments:  @assoc_table *t@ = pointer to an association table
+ *             @void *p@ = pointer to a block to remove
+ *
+ * Returns:    ---
+ *
+ * Use:                Removes an association from a table.
+ */
+
+void assoc_remove(assoc_table *t, void *p)
+{
+  assoc_base *q = p;
+  hash_remove(&t->t, &q->b);
+  x_free(t->t.a, q);
+  t->load++;
+}
+
+/* --- @assoc_mkiter@, @assoc_next@ --- *
+ *
+ * Arguments:  @assoc_iter *i@ = pointer to an iterator
+ *             @assoc_table *t@ = pointer to an association table
+ *
+ * Returns:    Next association, or null, for @assoc_next@; nothing for
+ *             @assoc_mkiter@.
+ *
+ * Use:                Iterates over the associations in a table.
+ */
+
+void assoc_mkiter(assoc_iter *i, assoc_table *t) { ASSOC_MKITER(i, t); }
+void *assoc_next(assoc_iter *i) { void *p; ASSOC_NEXT(i, p); return (p); }
+
+/*----- That's all, folks -------------------------------------------------*/
diff --git a/struct/assoc.h b/struct/assoc.h
new file mode 100644 (file)
index 0000000..6a40def
--- /dev/null
@@ -0,0 +1,148 @@
+/* -*-c-*-
+ *
+ * Assocation tables
+ *
+ * (c) 2000 Straylight/Edgeware
+ */
+
+/*----- Licensing notice --------------------------------------------------*
+ *
+ * This file is part of the mLib utilities library.
+ *
+ * mLib is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * mLib is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with mLib; if not, write to the Free
+ * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+ * MA 02111-1307, USA.
+ */
+
+#ifndef MLIB_ASSOC_H
+#define MLIB_ASSOC_H
+
+#ifdef __cplusplus
+  extern "C" {
+#endif
+
+/*----- Header files ------------------------------------------------------*/
+
+#ifndef MLIB_ATOM_H
+#  include "atom.h"
+#endif
+
+#ifndef MLIB_HASH_H
+#  include "hash.h"
+#endif
+
+/*----- Data structures ---------------------------------------------------*/
+
+typedef struct assoc_table {
+  hash_table t;
+  size_t load;
+} assoc_table;
+
+typedef struct assoc_base {
+  hash_base b;
+  atom *a;
+} assoc_base;
+
+typedef struct { hash_iter i; } assoc_iter;
+
+#define ASSOC_ATOM(p) (((assoc_base *)(p))->a + 0)
+
+/*----- Functions provided ------------------------------------------------*/
+
+/* --- @assoc_create@ --- *
+ *
+ * Arguments:  @assoc_table *t@ = pointer to an association table
+ *
+ * Returns:    ---
+ *
+ * Use:                Creates a new association table.
+ */
+
+extern void assoc_create(assoc_table */*t*/);
+
+/* --- @assoc_destroy@ --- *
+ *
+ * Arguments:  @assoc_table *t@ = pointer to an association table
+ *
+ * Returns:    ---
+ *
+ * Use:                Destroys an association table.
+ */
+
+extern void assoc_destroy(assoc_table */*t*/);
+
+/* --- @assoc_find@ --- *
+ *
+ * Arguments:  @assoc_table *t@ = pointer to an association table
+ *             @atom *a@ = an atom to label the item
+ *             @size_t sz@ = size of block to allocate
+ *             @unsigned *f@ = pointer to `found' flag
+ *
+ * Returns:    A pointer to the item located or null.
+ *
+ * Use:                Looks up an atom in an association table.  If the atom is
+ *             found, the association is returned.  If not, and @sz@ is
+ *             zero, a null pointer is returned.  Otherwise, a block of size
+ *             @sz@ is allocated, its @assoc_base@ header is filled in, and
+ *             the pointer returned.  The flag @*f@ is cleared if the item
+ *             couldn't be found, or set if it was.
+ *
+ *             All the atoms used in a particular table should come from the
+ *             same atom table.
+ */
+
+extern void *assoc_find(assoc_table */*t*/, atom */*a*/,
+                       size_t /*sz*/, unsigned */*f*/);
+
+/* --- @assoc_remove@ --- *
+ *
+ * Arguments:  @assoc_table *t@ = pointer to an association table
+ *             @void *p@ = pointer to a block to remove
+ *
+ * Returns:    ---
+ *
+ * Use:                Removes an association from a table.
+ */
+
+extern void assoc_remove(assoc_table */*t*/, void */*p*/);
+
+/* --- @assoc_mkiter@, @assoc_next@ --- *
+ *
+ * Arguments:  @assoc_iter *i@ = pointer to an iterator
+ *             @assoc_table *t@ = pointer to an association table
+ *
+ * Returns:    Next association, or null, for @assoc_next@; nothing for
+ *             @assoc_mkiter@.
+ *
+ * Use:                Iterates over the associations in a table.
+ */
+
+#define ASSOC_MKITER(i_, t_) HASH_MKITER(&(i_)->i, &(t_)->t)
+
+#define ASSOC_NEXT(i_, p) do {                                         \
+  hash_base *_q;                                                       \
+  HASH_NEXT(&(i_)->i, _q);                                             \
+  (p) = (void *)_q;                                                    \
+} while (0)
+
+extern void assoc_mkiter(assoc_iter */*i*/, assoc_table */*t*/);
+extern void *assoc_next(assoc_iter */*i*/);
+
+/*----- That's all, folks -------------------------------------------------*/
+
+#ifdef __cplusplus
+  }
+#endif
+
+#endif
diff --git a/struct/atom.3 b/struct/atom.3
new file mode 100644 (file)
index 0000000..65c655a
--- /dev/null
@@ -0,0 +1,149 @@
+.\" -*-nroff-*-
+.de VS
+.sp 1
+.RS
+.nf
+.ft B
+..
+.de VE
+.ft R
+.fi
+.RE
+.sp 1
+..
+.TH atom 3 "21 January 2001" "Straylight/Edgeware" "mLib utilities library"
+.SH NAME
+atom \- atom table manager
+.\" @atom_createtable
+.\" @atom_destroytable
+.\" @atom_intern
+.\" @atom_nintern
+.\" @atom_gensym
+.\" @atom_name
+.\" @atom_len
+.\" @atom_hash
+.\" @atom_mkiter
+.\" @atom_next
+.\"
+.\" @ATOM_GLOBAL
+.\" @INTERN
+.\" @GENSYM
+.\" @ATOM_NAME
+.\" @ATOM_LEN
+.\" @ATOM_HASH
+.\"
+.SH SYNOPSIS
+.nf
+.B "#include <mLib/atom.h>"
+
+.BI "void atom_createtable(atom_table *" t );
+.BI "void atom_destroytable(atom_table *" t );
+
+.BI "atom *atom_intern(atom_table *" t ", const char *" p );
+.BI "atom *atom_nintern(atom_table *" t ", const char *" p ", size_t " n );
+.BI "atom *atom_gensym(atom_table *" t );
+.BI "atom *INTERN(const char *" p );
+.BI "atom *GENSYM;"
+
+.BI "const char *atom_name(const atom *" a );
+.BI "size_t atom_len(const atom *" a );
+.BI "uint32 atom_hash(const atom *" a );
+.BI "const char *ATOM_NAME(const atom *" a );
+.BI "size_t ATOM_LEN(const atom *" a );
+.BI "uint32 ATOM_HASH(const atom *" a );
+
+.BI "void atom_mkiter(atom_iter *" i ", atom_table *" t );
+.BI "atom *atom_next(atom_iter *" i );
+
+.BI "extern atom_table *ATOM_GLOBAL;"
+.fi
+.SH DESCRIPTION
+The
+.B atom
+functions and macros implement a data type similar to immutable strings,
+with an additional property: that the addresses of two atoms from the
+same table are equal if and only if they contain the same text.  Atom
+strings don't have to be null-terminated, although the interface is
+simpler if you know that all of your atoms are null-terminated.  It's
+also possible to make
+.IR "uninterned atoms" :
+see below.
+.PP
+If a necessary memory allocation fails during an atom operation, the
+exception
+.B EXC_NOMEM
+is raised.
+.PP
+Atoms are useful for speeding up string comparisons, and for saving
+memory when many possibly-identical strings need storing.
+.PP
+There is a global atom table, named
+.BR ATOM_GLOBAL ,
+available for general use.  You can initialize your own atom table if
+either you want to ensure that the atoms are not shared with some other
+table, or if you want to be able to free the atoms later.  Private atom
+tables have the type
+.BR atom_table ;
+initialize it using the function
+.B atom_createtable
+and free it when you're finished using
+.BR atom_destroytable .
+.SS "Creating atoms from strings"
+The process of making atoms from strings is called
+.IR interning .
+The function
+.B atom_nintern
+takes an atom table, a string, and a length, and returns an atom with
+the same text.  If your string is null-terminated, you can instead use
+.B atom_intern
+which has no length argument; if, in addition, you want to use the
+global atom table, you can use the single-argument
+.B INTERN
+macro, which takes just a null-terminated string.
+.PP
+A terminating null byte is always appended to an atom name.  This is not
+considered to be a part of the name itself, and does not contribute to
+the atom's length as reported by the
+.B ATOM_LEN
+macro.
+.SS "Uninterned atoms"
+You can make an atom which is guaranteed to be distinct from every other
+atom, and has no sensible text string, by calling
+.BR atom_gensym ,
+passing it the address of your atom table.  The macro
+.B GENSYM
+(which doesn't look like a function, and has no parentheses following
+it!) will return a unique atom from the global table.  Uninterned atoms
+have a generated name of the form
+.RB ` *gen- \fInnn * '
+where
+.I nnn
+is an atom-table-specific sequence number.  This text is there as a
+debugging convenience, and doesn't mean that the uninterned atom has the
+same address as an interned atom with the same text.
+.SS "Other enquiries about atoms"
+Atoms can be interrogated to find their names and hashes.  The macro
+.B ATOM_NAME
+returns a pointer to the atom's name (its text);
+.B ATOM_LEN
+returns the length of the atom's name, excluding the terminating null;
+and
+.B ATOM_HASH
+returns the atom's hash value, which is useful if you want to use the
+atom as a key in some other structure.  There are lower-case function
+versions of these, which have the same effect.  There is little point in
+using the functions.
+.SS "Enumerating atoms"
+You can iterate over the atoms in an atom table.  The
+.B atom_mkiter
+function initializes an iterator object to iterate over a particular
+atom table;
+.B atom_next
+returns the next atom from the iterator.  Atoms are not returned in any
+particular order.
+.SH SEE ALSO
+.BR assoc (3),
+.BR hash (3),
+.BR mLib (3).
+.SH AUTHOR
+Mark Wooding, <mdw@distorted.org.uk>
diff --git a/struct/atom.c b/struct/atom.c
new file mode 100644 (file)
index 0000000..c91022d
--- /dev/null
@@ -0,0 +1,222 @@
+/* -*-c-*-
+ *
+ * Atom management
+ *
+ * (c) 2000 Straylight/Edgeware
+ */
+
+/*----- Licensing notice --------------------------------------------------*
+ *
+ * This file is part of the mLib utilities library.
+ *
+ * mLib is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * mLib is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with mLib; if not, write to the Free
+ * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+ * MA 02111-1307, USA.
+ */
+
+/*----- Header files ------------------------------------------------------*/
+
+#include <stdio.h>
+#include <string.h>
+
+#include "alloc.h"
+#include "atom.h"
+#include "hash.h"
+#include "sym.h"
+#include "unihash.h"
+
+/*----- Static variables --------------------------------------------------*/
+
+static atom_table atoms;
+
+/*----- Handy macros ------------------------------------------------------*/
+
+#define ATOM_RESOLVE(t) do {                                           \
+  if (t == ATOM_GLOBAL)                                                        \
+    t = &atoms;                                                                \
+  if (!t->t.t.v)                                                       \
+    atom_createtable(t);                                               \
+} while (0)
+
+/*----- Main code ---------------------------------------------------------*/
+
+/* --- @atom_createtable@ --- *
+ *
+ * Arguments:  @atom_table *t@ = pointer to an atom table
+ *
+ * Returns:    ---
+ *
+ * Use:                Initializes an atom table.
+ */
+
+void atom_createtable(atom_table *t)
+{
+  sym_create(&t->t);
+  t->g = 0;
+  t->gseq = 0;
+}
+
+/* --- @atom_destroytable@ --- *
+ *
+ * Arguments:  @atom_table *t@ = pointer to an atom table
+ *
+ * Returns:    ---
+ *
+ * Use:                Destroys all of the atoms in an atom table.  All of the atoms
+ *             (including uninterned atoms) are freed.  Any references to
+ *             atoms from the table become invalid, and any association
+ *             tables dependent on the atom table are unusable, except that
+ *             they may be destroyed safely.
+ */
+
+void atom_destroytable(atom_table *t)
+{
+  atom *a, *aa;
+
+  ATOM_RESOLVE(t);
+  for (a = (atom *)t->g; a; a = aa) {
+    aa = (atom *)a->b.b.next;
+    x_free(t->t.t.a, a);
+  }
+  sym_destroy(&t->t);
+}
+
+/* --- @atom_intern@, @atom_nintern@ --- *
+ *
+ * Arguments:  @atom_table *t@ = pointer to an atom table
+ *             @const char *p@ = pointer to the string to intern
+ *             @size_t n@ = size of the string (for @atom_nintern)
+ *
+ * Returns:    A pointer to the atom block for the given symbol string.
+ *
+ * Use:                Interns an atom, returning the atom block.  The string can be
+ *             extracted from the atom by means of the @ATOM_NAME@ macro.
+ */
+
+atom *atom_intern(atom_table *t, const char *p)
+{
+  atom *a;
+  unsigned f;
+
+  ATOM_RESOLVE(t);
+  a = sym_find(&t->t, p, -1, sizeof(atom), &f);
+  if (!f)
+    a->f = 0;
+  return (a);
+}
+
+atom *atom_nintern(atom_table *t, const char *p, size_t n)
+{
+  atom *a;
+  unsigned f;
+
+  ATOM_RESOLVE(t);
+  a = sym_find(&t->t, p, n, sizeof(atom), &f);
+  if (!f)
+    a->f = 0;
+  return (a);
+}
+
+/* --- @atom_gensym@ --- *
+ *
+ * Arguments:  @atom_table *t@ = pointer to a symbol table
+ *
+ * Returns:    A pointer to a new atom block, not previously interned.
+ *
+ * Use:                Creates a new, uninterned atom.  This atom will never be
+ *             returned by either @atom_intern@ or any other call to
+ *             @atom_gensym@, while the symbol table exists.
+ */
+
+atom *atom_gensym(atom_table *t)
+{
+  atom *a;
+  char buf[64];
+  size_t sz;
+
+  ATOM_RESOLVE(t);
+  sprintf(buf, "*gen-%lu*", t->gseq++);
+  sz = strlen(buf) + 1;
+  a = x_alloc(t->t.t.a, sizeof(atom) + sz);
+  a->b.name = (char *)(a + 1);
+  memcpy(a->b.name, buf, sz);
+  a->b.len = sz - 1;
+  a->b.b.hash = UNIHASH(&unihash_global, buf, sz);
+  a->f = ATOMF_GENSYM;
+  a->b.b.next = t->g;
+  t->g = &a->b.b;
+  return (a);
+}
+
+/* --- @atom_name@ --- *
+ *
+ * Arguments:  @atom *a@ = pointer to an atom
+ *
+ * Returns:    The atom's textual name.
+ *
+ * Use:                Given an atom, returns the name with which it was interned
+ *             (or a made-up name if it was created using @gensym@.
+ */
+
+const char *atom_name(const atom *a) { return ATOM_NAME(a); }
+
+/* --- @atom_len@ --- *
+ *
+ * Arguments:  @atom *a@ = pointer to an atom
+ *
+ * Returns:    The atom string's length.
+ *
+ * Use:                Given an atom, return the length of its textual
+ *             representation.
+ */
+
+size_t atom_len(const atom *a) { return ATOM_LEN(a); }
+
+/* --- @atom_hash@ --- *
+ *
+ * Arguments:  @atom *a@ = pointer to an atom
+ *
+ * Returns:    The atom's hash.
+ *
+ * Use:                Given an atom, returns its hash.
+ */
+
+uint32 atom_hash(const atom *a) { return ATOM_HASH(a); }
+
+/* --- @atom_mkiter@ , @atom_next@ --- *
+ *
+ * Arguments:  @atom_table *t@ = pointer to an atom table
+ *             @atom_iter *i@ = pointer to an iterator structure
+ *
+ * Returns:    Next atom, for @atom_next@; nothing for @atom_mkiter@.
+ *
+ * Use:                Iterates over atoms (both interned and uninterned).
+ */
+
+void atom_mkiter(atom_iter *i, atom_table *t)
+{
+  ATOM_RESOLVE(t);
+  i->i.i.t = &t->t.t;
+  i->i.i.p = t->g;
+  i->i.i.i = 0;
+}
+
+atom *atom_next(atom_iter *i)
+{
+  atom *a;
+  SYM_NEXT(&i->i, a);
+  return (a);
+}
+
+/*----- That's all, folks -------------------------------------------------*/
diff --git a/struct/atom.h b/struct/atom.h
new file mode 100644 (file)
index 0000000..430f23b
--- /dev/null
@@ -0,0 +1,183 @@
+/* -*-c-*-
+ *
+ * Atom management
+ *
+ * (c) 2000 Straylight/Edgeware
+ */
+
+/*----- Licensing notice --------------------------------------------------*
+ *
+ * This file is part of the mLib utilities library.
+ *
+ * mLib is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * mLib is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with mLib; if not, write to the Free
+ * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+ * MA 02111-1307, USA.
+ */
+
+#ifndef MLIB_ATOM_H
+#define MLIB_ATOM_H
+
+#ifdef __cplusplus
+  extern "C" {
+#endif
+
+/*----- Header files ------------------------------------------------------*/
+
+#ifndef MLIB_SYM_H
+#  include "sym.h"
+#endif
+
+/*----- Data structures ---------------------------------------------------*/
+
+typedef struct atom_table {
+  sym_table t;                         /* Symbol table of interned atoms */
+  hash_base *g;                                /* List of uninterned atoms */
+  unsigned long gseq;                  /* Sequence number for @gensym@ */
+} atom_table;
+
+typedef struct atom {
+  sym_base b;                          /* Base structure for symbol table */
+  unsigned f;                          /* Various useful flags */
+} atom;
+
+#define ATOMF_GENSYM 1u                        /* Atom is uninterned */
+
+typedef struct { sym_iter i; } atom_iter;
+
+/*----- Global magic ------------------------------------------------------*/
+
+#define ATOM_GLOBAL 0
+
+/*----- Functions provided ------------------------------------------------*/
+
+/* --- @atom_createtable@ --- *
+ *
+ * Arguments:  @atom_table *t@ = pointer to an atom table
+ *
+ * Returns:    ---
+ *
+ * Use:                Initializes an atom table.
+ */
+
+extern void atom_createtable(atom_table */*t*/);
+
+/* --- @atom_destroytable@ --- *
+ *
+ * Arguments:  @atom_table *t@ = pointer to an atom table
+ *
+ * Returns:    ---
+ *
+ * Use:                Destroys all of the atoms in an atom table.  All of the atoms
+ *             (including uninterned atoms) are freed.  Any references to
+ *             atoms from the table become invalid, and any association
+ *             tables dependent on the atom table are unusable, except that
+ *             they may be destroyed safely.
+ */
+
+extern void atom_destroytable(atom_table */*t*/);
+
+/* --- @atom_intern@ --- *
+ *
+ * Arguments:  @atom_table *t@ = pointer to an atom table
+ *             @const char *p@ = pointer to the string to intern
+ *             @size_t n@ = size of the string (for @atom_nintern)
+ *
+ * Returns:    A pointer to the atom block for the given symbol string.
+ *
+ * Use:                Interns an atom, returning the atom block.  The string can be
+ *             extracted from the atom by means of the @ATOM_NAME@ macro.
+ */
+
+#define INTERN(p) atom_intern(ATOM_GLOBAL, (p))
+
+extern atom *atom_intern(atom_table */*t*/, const char */*p*/);
+extern atom *atom_nintern(atom_table */*t*/,
+                         const char */*p*/, size_t /*n*/);
+
+/* --- @atom_gensym@ --- *
+ *
+ * Arguments:  @atom_table *t@ = pointer to a symbol table
+ *
+ * Returns:    A pointer to a new atom block, not previously interned.
+ *
+ * Use:                Creates a new, uninterned atom.  This atom will never be
+ *             returned by either @atom_intern@ or any other call to
+ *             @atom_gensym@, while the symbol table exists.
+ */
+
+#define GENSYM atom_gensym(ATOM_GLOBAL)
+
+extern atom *atom_gensym(atom_table */*t*/);
+
+/* --- @atom_name@ --- *
+ *
+ * Arguments:  @atom *a@ = pointer to an atom
+ *
+ * Returns:    The atom's textual name.
+ *
+ * Use:                Given an atom, returns the name with which it was interned
+ *             (or a made-up name if it was created using @gensym@.
+ */
+
+#define ATOM_NAME(a) SYM_NAME(a)
+
+extern const char *atom_name(const atom */*a*/);
+
+/* --- @atom_len@ --- *
+ *
+ * Arguments:  @atom *a@ = pointer to an atom
+ *
+ * Returns:    The atom string's length.
+ *
+ * Use:                Given an atom, return the length of its textual
+ *             representation.
+ */
+
+#define ATOM_LEN(a) SYM_LEN(a)
+
+extern size_t atom_len(const atom */*a*/);
+
+/* --- @atom_hash@ --- *
+ *
+ * Arguments:  @atom *a@ = pointer to an atom
+ *
+ * Returns:    The atom's hash.
+ *
+ * Use:                Given an atom, returns its hash.
+ */
+
+#define ATOM_HASH(a) SYM_HASH(a)
+
+extern uint32 atom_hash(const atom */*a*/);
+
+/* --- @atom_mkiter@ , @atom_next@ --- *
+ *
+ * Arguments:  @atom_table *t@ = pointer to an atom table
+ *             @atom_iter *i@ = pointer to an iterator structure
+ *
+ * Returns:    Next atom, for @atom_next@; nothing for @atom_mkiter@.
+ *
+ * Use:                Iterates over atoms (both interned and uninterned).
+ */
+
+extern void atom_mkiter(atom_iter */*i*/, atom_table */*t*/);
+extern atom *atom_next(atom_iter */*i*/);
+
+/*----- That's all, folks -------------------------------------------------*/
+
+#ifdef __cplusplus
+  }
+#endif
+
+#endif
diff --git a/struct/buf-dstr.c b/struct/buf-dstr.c
new file mode 100644 (file)
index 0000000..2d91b42
--- /dev/null
@@ -0,0 +1,75 @@
+/* -*-c-*-
+ *
+ * Buffers and dynamic strings
+ *
+ * (c) 2005 Straylight/Edgeware
+ */
+
+/*----- Licensing notice --------------------------------------------------*
+ *
+ * This file is part of the mLib utilities library.
+ *
+ * mLib is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * mLib is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with mLib; if not, write to the Free
+ * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+ * MA 02111-1307, USA.
+ */
+
+/*----- Header files ------------------------------------------------------*/
+
+#include <string.h>
+
+#include "buf.h"
+
+/*----- Main code ---------------------------------------------------------*/
+
+/* --- @buf_getdstr{8,{16,24,32,64}{,l,b},z} --- *
+ *
+ * Arguments:  @buf *b@ = pointer to a buffer block
+ *             @dstr *d@ = where to put the result
+ *
+ * Returns:    Zero if it worked, nonzero if there wasn't enough space.
+ *
+ * Use:                Gets a block of data from a buffer, and writes its contents
+ *             to a string.
+ */
+
+#define BUF_GETDSTR_(n, W, w)                                          \
+  int buf_getdstr##w(buf *b, dstr *d)                                  \
+  {                                                                    \
+    void *p;                                                           \
+    size_t sz;                                                         \
+                                                                       \
+    if ((p = buf_getmem##w(b, &sz)) == 0)                              \
+      return (-1);                                                     \
+    DPUTM(d, p, sz);                                                   \
+    return (0);                                                                \
+  }
+BUF_DOSUFFIXES(BUF_GETDSTR_)
+
+/* --- @buf_putdstr{8,{16,24,32,64}{,l,b},z} --- *
+ *
+ * Arguments:  @buf *b@ = pointer to a buffer block
+ *             @dstr *d@ = string to write
+ *
+ * Returns:    Zero if it worked, nonzero if there wasn't enough space.
+ *
+ * Use:                Puts a dynamic string to a buffer.
+ */
+
+#define BUF_PUTDSTR_(n, W, w)                                          \
+  int buf_putdstr##w(buf *b, dstr *d)                                  \
+    { return (buf_putmem##w(b, d->buf, d->len)); }
+BUF_DOSUFFIXES(BUF_PUTDSTR_)
+
+/*----- That's all, folks -------------------------------------------------*/
diff --git a/struct/buf.3 b/struct/buf.3
new file mode 100644 (file)
index 0000000..8964a09
--- /dev/null
@@ -0,0 +1,406 @@
+.\" -*-nroff-*-
+.de VS
+.sp 1
+.RS
+.nf
+.ft B
+..
+.de VE
+.ft R
+.fi
+.RE
+.sp 1
+..
+.de hP
+.IP
+.ft B
+\h'-\w'\\$1\ 'u'\\$1\ \c
+.ft P
+..
+.ie t .ds o \(bu
+.el .ds o o
+.TH buf 3 "23 September 2005" "Straylight/Edgeware" "mLib utilities library"
+.SH NAME
+buf \- reading and writing stuff in buffers
+.\" @BBASE
+.\" @BLIM
+.\" @BCUR
+.\" @BSZ
+.\" @BLEN
+.\" @BLEFT
+.\" @BSTEP
+.\" @BBAD
+.\" @BOK
+.\" @BENSURE
+.
+.\" @buf_init
+.\" @buf_break
+.\" @buf_flip
+.\" @buf_ensure
+.\" @buf_get
+.\" @buf_put
+.
+.\" @buf_getbyte
+.\" @buf_putbyte
+.
+.\" @buf_getu8
+.\" @buf_getu16
+.\" @buf_getu16b
+.\" @buf_getu16l
+.\" @buf_getu24
+.\" @buf_getu24b
+.\" @buf_getu24l
+.\" @buf_getu32
+.\" @buf_getu32b
+.\" @buf_getu32l
+.
+.\" @buf_putu8
+.\" @buf_putu16
+.\" @buf_putu16b
+.\" @buf_putu16l
+.\" @buf_putu24
+.\" @buf_putu24b
+.\" @buf_putu24l
+.\" @buf_putu32
+.\" @buf_putu32b
+.\" @buf_putu32l
+.
+.\" @buf_getbuf8
+.\" @buf_getbuf16
+.\" @buf_getbuf16b
+.\" @buf_getbuf16l
+.\" @buf_getbuf24
+.\" @buf_getbuf24b
+.\" @buf_getbuf24l
+.\" @buf_getbuf32
+.\" @buf_getbuf32b
+.\" @buf_getbuf32l
+.\" @buf_getbufz
+.
+.\" @buf_putbuf8
+.\" @buf_putbuf16
+.\" @buf_putbuf16b
+.\" @buf_putbuf16l
+.\" @buf_putbuf24
+.\" @buf_putbuf24b
+.\" @buf_putbuf24l
+.\" @buf_putbuf32
+.\" @buf_putbuf32b
+.\" @buf_putbuf32l
+.\" @buf_putbufz
+.
+.\" @buf_getmem16
+.\" @buf_getmem16b
+.\" @buf_getmem16l
+.\" @buf_getmem24
+.\" @buf_getmem24b
+.\" @buf_getmem24l
+.\" @buf_getmem32
+.\" @buf_getmem32b
+.\" @buf_getmem32l
+.\" @buf_getmem8
+.\" @buf_getmemz
+.
+.\" @buf_putmem8
+.\" @buf_putmem16
+.\" @buf_putmem16b
+.\" @buf_putmem16l
+.\" @buf_putmem24
+.\" @buf_putmem24b
+.\" @buf_putmem24l
+.\" @buf_putmem32
+.\" @buf_putmem32b
+.\" @buf_putmem32l
+.\" @buf_putmemz
+.
+.\" @buf_putstr8
+.\" @buf_putstr16
+.\" @buf_putstr16b
+.\" @buf_putstr16l
+.\" @buf_putstr24
+.\" @buf_putstr24b
+.\" @buf_putstr24l
+.\" @buf_putstr32
+.\" @buf_putstr32b
+.\" @buf_putstr32l
+.\" @buf_putstrz
+.
+.\" @buf_getdstr8
+.\" @buf_getdstr16
+.\" @buf_getdstr16b
+.\" @buf_getdstr16l
+.\" @buf_getdstr24
+.\" @buf_getdstr24b
+.\" @buf_getdstr24l
+.\" @buf_getdstr32
+.\" @buf_getdstr32b
+.\" @buf_getdstr32l
+.\" @buf_getdstrz
+.
+.\" @buf_putdstr8
+.\" @buf_putdstr16
+.\" @buf_putdstr16b
+.\" @buf_putdstr16l
+.\" @buf_putdstr24
+.\" @buf_putdstr24b
+.\" @buf_putdstr24l
+.\" @buf_putdstr32
+.\" @buf_putdstr32b
+.\" @buf_putdstr32l
+.\" @buf_putdstrz
+.SH SYNOPSIS
+.nf
+.B "#include <mLib/dstr.h>"
+
+.BI "void buf_init(buf *" b ", void *" p ", size_t " sz );
+.BI "void buf_flip(buf *" b );
+.BI "octet *BBASE(buf *" b );
+.BI "octet *BLIM(buf *" b );
+.BI "octet *BCUR(buf *" b );
+.BI "ptrdiff_t BSZ(buf *" b );
+.BI "ptrdiff_t BLEN(buf *" b );
+.BI "ptrdiff_t BLEFT(buf *" b );
+
+.BI "int buf_break(buf *" b );
+.BI "int BBAD(buf *" b );
+.BI "int BOK(buf *" b );
+
+.BI "int buf_ensure(buf *" b ", size_t " sz );
+.BI "int BENSURE(buf *" b ", size_t " sz );
+.BI "octet *BSTEP(buf *" b ", size_t " sz );
+
+.BI "void *buf_get(buf *" b ", size_t " sz );
+.BI "void *buf_put(buf *" b ", const void *" p ", size_t " sz );
+
+.BI "int buf_getbyte(buf *" b );
+.BI "int buf_putbyte(buf *" b ", int ch" );
+.BI "int buf_getu" suff "(buf *" b ", uint" suff " *" w );
+.BI "int buf_putu" suff "(buf *" b ", uint" suff " " w );
+.BI "void *buf_getmem" suff "(buf *" b ", size_t *" sz );
+.BI "int buf_putmem" suff "(buf *" b ", const void *" p ", size_t " sz );
+.BI "int buf_getbuf" suff "(buf *" b ", buf *" bb );
+.BI "int buf_putbuf" suff "(buf *" b ", buf *" bb );
+.BI "int buf_getdstr" suff "(buf *" b ", dstr *" d );
+.BI "int buf_putdstr" suff "(buf *" b ", dstr *" d );
+.BI "int buf_putstr" suff "(buf *" b ", const char *" p );
+.fi
+.SH DESCRIPTION
+The
+.B buf
+interface allows relatively convenient reading and writing of structured
+binary data from and to fixed-size memory buffers.  It's useful for
+formatting and parsing down network data packets, for example.
+.SS "Buffer basics"
+A buffer has three important pointers associated with it:
+.TP
+.I base
+The base address of the buffer.
+.TP
+.I limit
+Just past the last usable byte in the buffer
+.TP
+.I current
+The position in the buffer at which the next read or write will occur.
+.PP
+A buffer is created using the
+.B buf_init
+function.  You must pass it the buffer base address and size, and a
+pointer to a
+.B buf
+structure to fill in.  It doesn't allocate any memory, so you don't need
+to dispose of the
+.B buf
+structure in any way before forgetting about it.
+.PP
+A collection of macros is provided for finding the positions of the
+various interesting pointers known about a buffer, and the sizes of the
+regions of memory they imply.
+.TP
+.B BBASE
+The buffer's
+.I base
+pointer.
+.TP
+.B BLIM
+The buffer's
+.I limit
+pointer.
+.TP
+.B BCUR
+The buffer's
+.I current
+pointer.
+.TP
+.B BSZ
+The size of the buffer; i.e.,
+.I limit
+\-
+.IR base .
+.TP
+.B BLEN
+The length of data in the buffer (if writing) or the amount of data
+read (if reading); i.e.,
+.I current
+\-
+.IR base .
+.TP
+.B BLEFT
+The amount of space left in the buffer (if writing) or the amount of
+data yet to read (if reading); i.e.,
+.I limit
+\-
+.IR current .
+.PP
+The function
+.B buf_flip
+takes a buffer which has been used for writing, and makes it suitable
+for reading.  This turns out to be useful when building packets in
+multi-layered networking software.  Its precise behaviour is to preserve
+.IR base ,
+to set
+.I limit
+to
+.IR current ,
+and to set
+.I current
+to
+.IR base .
+.PP
+A buffer can be
+.IR broken ,
+to indicate that it has overflowed or that its contents are otherwise
+invalid.  The various buffer access functions described below all fail
+on a broken buffer, and any errors they encounter cause the buffer to
+become broken.  Most simple programs which only use the supplied buffer
+access functions can avoid the tedium of error-checking every function
+call and just check the brokenness state at the end of their run.
+.PP
+The function
+.B buf_break
+will break a buffer.  The macro
+.B BBAD
+reports true (nonzero) if its buffer argument is broken, or false (zero)
+otherwise; its counterpart
+.B BOK
+reports true if the buffer is OK, and false if it is broken.
+.SS "Low-level buffer access"
+Access to the data in the buffer is usually sequential.  The
+.B BENSURE
+macro (or the equivalent
+.B buf_ensure
+function) checks that the buffer is OK and that there is enough space
+remaining in the buffer for
+.I sz
+bytes: if so, it returns zero; otherwise it breaks the buffer and
+returns \-1.
+.PP
+The
+.B BSTEP
+macro advances the buffer's
+.I current
+pointer by
+.I sz
+bytes.  It does no bounds checking.  Together with
+.BR BENSURE ,
+this provides sequential access to the buffer's contents.
+.PP
+The
+.B buf_get
+function is the basis of most buffer access functions, whether for
+reading or writing.  If the buffer is OK, and there are
+.I sz
+or more bytes remaining, it steps the buffer's
+.I current
+pointer by
+.I sz
+and returns the
+.I original
+(pre-stepping)
+.I current
+pointer; otherwise it breaks the buffer if necessary, and returns a null
+pointer.
+.PP
+The
+.B buf_put
+function writes
+.I sz
+bytes of data starting at
+.I p
+to the buffer.  If it succeeded, it returns 0; otherwise it returns \-1.
+.SS "Formatted buffer access"
+The function
+.B buf_getbyte
+returns the next byte from a buffer as a nonnegative integer, or \-1 on
+error.  The function
+.B buf_putbyte
+writes its argument to a buffer, and returns 0 on succes; it returns \-1
+if it failed.
+.PP
+Many of the remaining functions deal with integer formatting and buffer
+lengths.  The functions support 8-, 16-, 24- and 32-bit integers, in
+big- or little-endian order; on platforms with 64-bit integers, these
+are supported too.  The functions' names carry a suffix which is the
+width in bits of the integers they deal with and an optional
+.RB ` l '
+for little- or
+.RB ` b '
+for big-endian byte order.  (The variant with no letter uses big-endian
+order.  Use of these variants tends to mean `I don't really care, but be
+consistent,' and is not recommended if you have an externally-defined
+spec you're meant to be compatible with.)
+.PP
+The function
+.BI buf_getu suff
+reads an integer.  On success, it stores the integer it read at the
+address
+.I w
+given, and returns zero; on failure, it returns \-1.  The function
+.BI buf_putu suff
+write an integer.  It returns zero on success or \-1 on failure.
+.PP
+Functions which deal with block lengths assume the length is prefixed to
+the data, and don't include themselves.  They also have an additional
+.RB ` z '
+variant, which deals with zero-terminated data.  No checks are done on
+writing that the data written contains no zero bytes.
+.PP
+The function
+.BI buf_getmem suff
+fetches a block of data.  On success, it returns its base address and
+stores its length at the given address; on failure, it returns null.
+The function
+.BI buf_putmem suff
+writes a block of data; it return zero on success or \-1 on failure.
+.PP
+The functon
+.BI buf_getbuf suff
+fetches a block of data and makes a second buffer point to it, i.e.,
+setting its
+.I base
+and
+.I current
+pointers to the start of the block and its
+.I limit
+pointer to just past the end.  No copying of bulk data is performed.
+The function
+.BI buf_putbuf suff
+writes the contents of a buffer (i.e., between its
+.I base
+and
+.I current
+pointers).  The function
+.BI buf_getdstr suff
+fetches a block of data and append it to a dynamic string (see
+.BR dstr (3)).
+The function
+.BI buf_putdstr suff
+writes the contents of a dynamic string to a buffer.  Finally, the
+function
+.BI buf_putstr suff
+writes a standard C null-terminated string to a buffer.  All these
+functions return zero on success or \-1 on failure.
+.SH "SEE ALSO"
+.BR dstr (3),
+.BR mLib (3).
+.SH AUTHOR
+Mark Wooding, <mdw@distorted.org.uk>
diff --git a/struct/buf.c b/struct/buf.c
new file mode 100644 (file)
index 0000000..bd16ec7
--- /dev/null
@@ -0,0 +1,353 @@
+/* -*-c-*-
+ *
+ * Buffer handling
+ *
+ * (c) 2001 Straylight/Edgeware
+ */
+
+/*----- Licensing notice --------------------------------------------------*
+ *
+ * This file is part of the mLib utilities library.
+ *
+ * mLib is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * mLib is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with mLib; if not, write to the Free
+ * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+ * MA 02111-1307, USA.
+ */
+
+/*----- Header files ------------------------------------------------------*/
+
+#include <assert.h>
+#include <string.h>
+
+#include "buf.h"
+#include "macros.h"
+
+/*----- Main code ---------------------------------------------------------*/
+
+/* --- @buf_init@ --- *
+ *
+ * Arguments:  @buf *b@ = pointer to a buffer block
+ *             @void *p@ = pointer to a buffer
+ *             @size_t sz@ = size of the buffer
+ *
+ * Returns:    ---
+ *
+ * Use:                Initializes the buffer block appropriately.
+ */
+
+void buf_init(buf *b, void *p, size_t sz)
+{
+  b->base = b->p = p;
+  b->limit = b->p + sz;
+  b->f = 0;
+}
+
+/* --- @buf_break@ --- *
+ *
+ * Arguments:  @buf *b@ = pointer to a buffer block
+ *
+ * Returns:    Some negative value.
+ *
+ * Use:                Marks a buffer as broken.
+ */
+
+int buf_break(buf *b) { b->f |= BF_BROKEN; return (-1); }
+
+/* --- @buf_flip@ --- *
+ *
+ * Arguments:  @buf *b@ = pointer to a buffer block
+ *
+ * Returns:    ---
+ *
+ * Use:                Flips a buffer so that if you've just been writing to it,
+ *             you can now read from the bit you've written.
+ */
+
+void buf_flip(buf *b)
+{
+  b->limit = b->p;
+  b->p = b->base;
+}
+
+/* --- @buf_ensure@ --- *
+ *
+ * Arguments:  @buf *b@ = pointer to a buffer block
+ *             @size_t sz@ = size of data wanted
+ *
+ * Returns:    Zero if it worked, nonzero if there wasn't enough space.
+ *
+ * Use:                Ensures that there are @sz@ bytes still in the buffer.
+ */
+
+int buf_ensure(buf *b, size_t sz) { return (BENSURE(b, sz)); }
+
+/* --- @buf_get@ --- *
+ *
+ * Arguments:  @buf *b@ = pointer to a buffer block
+ *             @size_t sz@ = size of the buffer
+ *
+ * Returns:    Pointer to the place in the buffer.
+ *
+ * Use:                Reserves a space in the buffer of the requested size, and
+ *             returns its start address.
+ */
+
+void *buf_get(buf *b, size_t sz)
+{
+  void *p;
+  if (BENSURE(b, sz))
+    return (0);
+  p = BCUR(b);
+  BSTEP(b, sz);
+  return (p);
+}
+
+/* --- @buf_put@ --- *
+ *
+ * Arguments:  @buf *b@ = pointer to a buffer block
+ *             @const void *p@ = pointer to a buffer
+ *             @size_t sz@ = size of the buffer
+ *
+ * Returns:    Zero if it worked, nonzero if there wasn't enough space.
+ *
+ * Use:                Fetches data from some place and puts it in the buffer
+ */
+
+int buf_put(buf *b, const void *p, size_t sz)
+{
+  if (BENSURE(b, sz))
+    return (-1);
+  memcpy(BCUR(b), p, sz);
+  BSTEP(b, sz);
+  return (0);
+}
+
+/* --- @buf_getbyte@ --- *
+ *
+ * Arguments:  @buf *b@ = pointer to a buffer block
+ *
+ * Returns:    A byte, or less than zero if there wasn't a byte there.
+ *
+ * Use:                Gets a single byte from a buffer.
+ */
+
+int buf_getbyte(buf *b)
+{
+  if (BENSURE(b, 1))
+    return (-1);
+  return (*b->p++);
+}
+
+/* --- @buf_putbyte@ --- *
+ *
+ * Arguments:  @buf *b@ = pointer to a buffer block
+ *             @int ch@ = byte to write
+ *
+ * Returns:    Zero if OK, nonzero if there wasn't enough space.
+ *
+ * Use:                Puts a single byte in a buffer.
+ */
+
+int buf_putbyte(buf *b, int ch)
+{
+  if (BENSURE(b, 1))
+    return (-1);
+  *b->p++ = ch;
+  return (0);
+}
+
+/* --- @buf_getu{8,{16,24,32,64}{,l,b}}@ --- *
+ *
+ * Arguments:  @buf *b@ = pointer to a buffer block
+ *             @uintSZ *w@ = where to put the word
+ *
+ * Returns:    Zero if OK, or nonzero if there wasn't a word there.
+ *
+ * Use:                Gets a word of appropriate size and order from a buffer.
+ */
+
+#define BUF_GETU_(n, W, w)                                             \
+  int buf_getu##w(buf *b, uint##n *ww)                                 \
+  {                                                                    \
+    if (BENSURE(b, SZ_##W)) return (-1);                               \
+    *ww = LOAD##W(b->p);                                               \
+    BSTEP(b, SZ_##W);                                                  \
+    return (0);                                                                \
+  }
+DOUINTCONV(BUF_GETU_)
+
+/* --- @buf_putu{8,{16,24,32,64}{,l,b}}@ --- *
+ *
+ * Arguments:  @buf *b@ = pointer to a buffer block
+ *             @uintSZ w@ = word to write
+ *
+ * Returns:    Zero if OK, or nonzero if there wasn't enough space
+ *
+ * Use:                Puts a word into a buffer with appropriate size and order.
+ */
+
+#define BUF_PUTU_(n, W, w)                                             \
+  int buf_putu##w(buf *b, uint##n ww)                                  \
+  {                                                                    \
+    if (BENSURE(b, SZ_##W)) return (-1);                               \
+    STORE##W(b->p, ww);                                                        \
+    BSTEP(b, SZ_##W);                                                  \
+    return (0);                                                                \
+  }
+DOUINTCONV(BUF_PUTU_)
+
+/* --- @findz@ --- *
+ *
+ * Arguments:  @buf *b@ = pointer to a buffer block
+ *             @size_t *nn@ = where to put the length
+ *
+ * Returns:    Zero if OK, nonzero if there wasn't a null byte to be found.
+ *
+ * Use:                Finds a terminating null byte.  The length includes this
+ *             terminator.
+ */
+
+static int findz(buf *b, size_t *nn)
+{
+  octet *p;
+
+  if ((p = memchr(BCUR(b), 0, BLEFT(b))) == 0) {
+    buf_break(b);
+    return (-1);
+  }
+  *nn = p - BCUR(b) + 1;
+  return (0);
+}
+
+/* --- @buf_getmem{8,{16,24,32,64}{,l,b},z} --- *
+ *
+ * Arguments:  @buf *b@ = pointer to a buffer block
+ *             @size_t *nn@ = where to put the length
+ *
+ * Returns:    Pointer to the buffer data, or null.
+ *
+ * Use:                Gets a chunk of memory from a buffer.  The suffix is the
+ *             width and byte order of the length; @z@ means null-
+ *             terminated.
+ */
+
+#define BUF_GETMEM_(n, W, w)                                           \
+  void *buf_getmem##w(buf *b, size_t *nn)                              \
+  {                                                                    \
+    uint##n sz;                                                                \
+    if (buf_getu##w(b, &sz)) return (0);                               \
+    if (BENSURE(b, sz)) return (0);                                    \
+    *nn = sz;                                                          \
+    return (buf_get(b, sz));                                           \
+  }
+DOUINTCONV(BUF_GETMEM_)
+
+void *buf_getmemz(buf *b, size_t *nn)
+{
+  if (findz(b, nn)) return (0);
+  return (buf_get(b, *nn));
+}
+
+/* --- @buf_putmem{8,{16,24,32,64}{,l,b},z} --- *
+ *
+ * Arguments:  @buf *b@ = pointer to a buffer block
+ *             @const void *p@ = pointer to data to write
+ *             @size_t n@ = length to write
+ *
+ * Returns:    Zero if OK, nonzero if there wasn't enough space.
+ *
+ * Use:                Writes a chunk of data to a buffer.  The suffix is the
+ *             width and byte order of the length; @z@ means null-
+ *             terminated.
+ */
+
+#define BUF_PUTMEM_(n, W, w)                                           \
+  int buf_putmem##w(buf *b, const void *p, size_t sz)                  \
+  {                                                                    \
+    MUFFLE_WARNINGS_STMT                                               \
+      (CLANG_WARNING("-Wtautological-constant-out-of-range-compare"),  \
+       { assert(sz <= MASK##W); });                                    \
+    if (buf_putu##w(b, sz) || buf_put(b, p, sz))                       \
+      return (-1);                                                     \
+    return (0);                                                                \
+  }
+DOUINTCONV(BUF_PUTMEM_)
+
+int buf_putmemz(buf *b, const void *p, size_t n)
+{
+  octet *q;
+
+  assert(!memchr(p, 0, n));
+  if ((q = buf_get(b, n + 1)) == 0)
+    return (-1);
+  memcpy(q, p, n);
+  q[n] = 0;
+  return (0);
+}
+
+/* --- @buf_getbuf{8,{16,24,32,64}{,l,b},z} --- *
+ *
+ * Arguments:  @buf *b@ = pointer to a buffer block
+ *             @buf *bb@ = where to put the result
+ *
+ * Returns:    Zero if it worked, nonzero if there wasn't enough space.
+ *
+ * Use:                Gets a block of data from a buffer, and writes its bounds to
+ *             another buffer.
+ */
+
+#define BUF_GETBUF_(n, W, w)                                           \
+  int buf_getbuf##w(buf *b, buf *bb)                                   \
+  {                                                                    \
+    void *p;                                                           \
+    size_t sz;                                                         \
+                                                                       \
+    if ((p = buf_getmem##w(b, &sz)) == 0)                              \
+      return (-1);                                                     \
+    buf_init(bb, p, sz);                                               \
+    return (0);                                                                \
+  }
+BUF_DOSUFFIXES(BUF_GETBUF_)
+
+/* --- @buf_putbuf{8,{16,24,32,64}{,l,b},z} --- *
+ *
+ * Arguments:  @buf *b@ = pointer to a buffer block
+ *             @buf *bb@ = buffer to write
+ *
+ * Returns:    Zero if it worked, nonzero if there wasn't enough space.
+ *
+ * Use:                Puts the contents of a buffer to a buffer.
+ */
+
+#define BUF_PUTBUF_(n, W, w)                                           \
+  int buf_putbuf##w(buf *b, buf *bb)                                   \
+    { return (buf_putmem##w(b, BBASE(bb), BLEN(bb))); }
+BUF_DOSUFFIXES(BUF_PUTBUF_)
+
+/* --- @buf_putstr{8,{16,24,32,64}{,l,b},z} --- *
+ *
+ * Arguments:  @buf *b@ = pointer to a buffer block
+ *             @const char *p@ = string to write
+ *
+ * Returns:    Zero if it worked, nonzero if there wasn't enough space.
+ *
+ * Use:                Puts a null-terminated string to a buffer.
+ */
+
+#define BUF_PUTSTR_(n, W, w)                                           \
+  int buf_putstr##w(buf *b, const char *p)                             \
+    { return (buf_putmem##w(b, p, strlen(p))); }
+BUF_DOSUFFIXES(BUF_PUTSTR_)
+
+/*----- That's all, folks -------------------------------------------------*/
diff --git a/struct/buf.h b/struct/buf.h
new file mode 100644 (file)
index 0000000..a3585b4
--- /dev/null
@@ -0,0 +1,327 @@
+/* -*-c-*-
+ *
+ * Reading and writing packet buffers
+ *
+ * (c) 2001 Straylight/Edgeware
+ */
+
+/*----- Licensing notice --------------------------------------------------*
+ *
+ * This file is part of the mLib utilities library.
+ *
+ * mLib is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * mLib is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with mLib; if not, write to the Free
+ * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+ * MA 02111-1307, USA.
+ */
+
+#ifndef MLIB_BUF_H
+#define MLIB_BUF_H
+
+#ifdef __cplusplus
+  extern "C" {
+#endif
+
+/*----- Header files ------------------------------------------------------*/
+
+#include <stddef.h>
+
+#ifndef MLIB_BITS_H
+#  include "bits.h"
+#endif
+
+#ifndef MLIB_DSTR_H
+#  include "dstr.h"
+#endif
+
+#ifndef MLIB_MACROS_H
+#  include "macros.h"
+#endif
+
+/*----- Data structures ---------------------------------------------------*/
+
+/* --- Buffers --- *
+ *
+ * Buffers provide a simple stream-like interface for building and parsing
+ * packets.
+ */
+
+typedef struct buf {
+  octet *base, *p, *limit;             /* Pointers to the buffer */
+  unsigned f;                          /* Various flags */
+} buf;
+
+#define BF_BROKEN 1u                   /* Buffer is broken */
+
+/*----- Useful macros -----------------------------------------------------*/
+
+#define BBASE(b) ((b)->base)
+#define BLIM(b) ((b)->limit)
+#define BCUR(b) ((b)->p)
+#define BSZ(b) ((b)->limit - (b)->base)
+#define BLEN(b) ((b)->p - (b)->base)
+#define BLEFT(b) ((b)->limit - (b)->p)
+#define BSTEP(b, sz) ((b)->p += (sz))
+#define BBAD(b) ((b)->f & BF_BROKEN)
+#define BOK(b) (!BBAD(b))
+
+#if GCC_VERSION_P(8, 0)
+#  define BENSURE(b, sz)                                               \
+     MUFFLE_WARNINGS_EXPR(GCC_WARNING("-Wint-in-bool-context"),                \
+       (BBAD(b) ? -1 : (sz) > BLEFT(b) ? (b)->f |= BF_BROKEN, -1 : 0))
+#else
+#  define BENSURE(b, sz)                                               \
+     (BBAD(b) ? -1 : (sz) > BLEFT(b) ? (b)->f |= BF_BROKEN, -1 : 0)
+#endif
+
+#define BUF_DOSUFFIXES(_) DOUINTCONV(_) _(z, z, z)
+
+/*----- Functions provided ------------------------------------------------*/
+
+/* --- @buf_init@ --- *
+ *
+ * Arguments:  @buf *b@ = pointer to a buffer block
+ *             @void *p@ = pointer to a buffer
+ *             @size_t sz@ = size of the buffer
+ *
+ * Returns:    ---
+ *
+ * Use:                Initializes the buffer block appropriately.
+ */
+
+extern void buf_init(buf */*b*/, void */*p*/, size_t /*sz*/);
+
+/* --- @buf_break@ --- *
+ *
+ * Arguments:  @buf *b@ = pointer to a buffer block
+ *
+ * Returns:    Some negative value.
+ *
+ * Use:                Marks a buffer as broken.
+ */
+
+extern int buf_break(buf */*b*/);
+
+/* --- @buf_flip@ --- *
+ *
+ * Arguments:  @buf *b@ = pointer to a buffer block
+ *
+ * Returns:    ---
+ *
+ * Use:                Flips a buffer so that if you've just been writing to it,
+ *             you can now read from the bit you've written.
+ */
+
+extern void buf_flip(buf */*b*/);
+
+/* --- @buf_ensure@ --- *
+ *
+ * Arguments:  @buf *b@ = pointer to a buffer block
+ *             @size_t sz@ = size of data wanted
+ *
+ * Returns:    Zero if it worked, nonzero if there wasn't enough space.
+ *
+ * Use:                Ensures that there are @sz@ bytes still in the buffer.
+ */
+
+extern int buf_ensure(buf */*b*/, size_t /*sz*/);
+
+/* --- @buf_get@ --- *
+ *
+ * Arguments:  @buf *b@ = pointer to a buffer block
+ *             @size_t sz@ = size of the buffer
+ *
+ * Returns:    Pointer to the place in the buffer.
+ *
+ * Use:                Reserves a space in the buffer of the requested size, and
+ *             returns its start address.
+ */
+
+extern void *buf_get(buf */*b*/, size_t /*sz*/);
+
+/* --- @buf_put@ --- *
+ *
+ * Arguments:  @buf *b@ = pointer to a buffer block
+ *             @const void *p@ = pointer to a buffer
+ *             @size_t sz@ = size of the buffer
+ *
+ * Returns:    Zero if it worked, nonzero if there wasn't enough space.
+ *
+ * Use:                Fetches data from some place and puts it in the buffer
+ */
+
+extern int buf_put(buf */*b*/, const void */*p*/, size_t /*sz*/);
+
+/* --- @buf_getbyte@ --- *
+ *
+ * Arguments:  @buf *b@ = pointer to a buffer block
+ *
+ * Returns:    A byte, or less than zero if there wasn't a byte there.
+ *
+ * Use:                Gets a single byte from a buffer.
+ */
+
+extern int buf_getbyte(buf */*b*/);
+
+/* --- @buf_putbyte@ --- *
+ *
+ * Arguments:  @buf *b@ = pointer to a buffer block
+ *             @int ch@ = byte to write
+ *
+ * Returns:    Zero if OK, nonzero if there wasn't enough space.
+ *
+ * Use:                Puts a single byte in a buffer.
+ */
+
+extern int buf_putbyte(buf */*b*/, int /*ch*/);
+
+/* --- @buf_getu{8,{16,24,32,64}{,l,b}}@ --- *
+ *
+ * Arguments:  @buf *b@ = pointer to a buffer block
+ *             @uintSZ *w@ = where to put the word
+ *
+ * Returns:    Zero if OK, or nonzero if there wasn't a word there.
+ *
+ * Use:                Gets a word of appropriate size and order from a buffer.
+ */
+
+#define BUF_DECL_GETU_(n, W, w)                                                \
+  extern int buf_getu##w(buf */*b*/, uint##n */*w*/);
+DOUINTCONV(BUF_DECL_GETU_)
+
+/* --- @buf_putu{8,{16,24,32,64}{,l,b}}@ --- *
+ *
+ * Arguments:  @buf *b@ = pointer to a buffer block
+ *             @uintSZ w@ = word to write
+ *
+ * Returns:    Zero if OK, or nonzero if there wasn't enough space
+ *
+ * Use:                Puts a word into a buffer with appropriate size and order.
+ */
+
+#define BUF_DECL_PUTU_(n, W, w)                                                \
+  extern int buf_putu##w(buf */*b*/, uint##n /*w*/);
+DOUINTCONV(BUF_DECL_PUTU_)
+
+/* --- @buf_getmem{8,{16,24,32,64}{,l,b},z} --- *
+ *
+ * Arguments:  @buf *b@ = pointer to a buffer block
+ *             @size_t *nn@ = where to put the length
+ *
+ * Returns:    Pointer to the buffer data, or null.
+ *
+ * Use:                Gets a chunk of memory from a buffer.  The suffix is the
+ *             width and byte order of the length; @z@ means null-
+ *             terminated.
+ */
+
+#define BUF_DECL_GETMEM_(n, W, w)                                      \
+  extern void *buf_getmem##w(buf */*b*/, size_t */*nn*/);
+BUF_DOSUFFIXES(BUF_DECL_GETMEM_)
+
+/* --- @buf_putmem{8,{16,24,32,64}{,l,b},z} --- *
+ *
+ * Arguments:  @buf *b@ = pointer to a buffer block
+ *             @const void *p@ = pointer to data to write
+ *             @size_t n@ = length to write
+ *
+ * Returns:    Zero if OK, nonzero if there wasn't enough space.
+ *
+ * Use:                Writes a chunk of data to a buffer.  The suffix is the
+ *             width and byte order of the length; @z@ means null-
+ *             terminated.
+ */
+
+#define BUF_DECL_PUTMEM_(n, W, w)                                      \
+  extern int buf_putmem##w(buf */*b*/, const void */*p*/, size_t /*nn*/);
+BUF_DOSUFFIXES(BUF_DECL_PUTMEM_)
+
+/* --- @buf_getbuf{8,{16,24,32,64}{,l,b},z} --- *
+ *
+ * Arguments:  @buf *b@ = pointer to a buffer block
+ *             @buf *bb@ = where to put the result
+ *
+ * Returns:    Zero if it worked, nonzero if there wasn't enough space.
+ *
+ * Use:                Gets a block of data from a buffer, and writes its bounds to
+ *             another buffer.
+ */
+
+#define BUF_DECL_GETBUF_(n, W, w)                                      \
+  extern int buf_getbuf##w(buf */*b*/, buf */*bb*/);
+BUF_DOSUFFIXES(BUF_DECL_GETBUF_)
+
+/* --- @buf_putbuf{8,{16,24,32,64}{,l,b},z} --- *
+ *
+ * Arguments:  @buf *b@ = pointer to a buffer block
+ *             @buf *bb@ = buffer to write
+ *
+ * Returns:    Zero if it worked, nonzero if there wasn't enough space.
+ *
+ * Use:                Puts the contents of a buffer to a buffer.
+ */
+
+#define BUF_DECL_PUTBUF_(n, W, w)                                      \
+  extern int buf_putbuf##w(buf */*b*/, buf */*bb*/);
+BUF_DOSUFFIXES(BUF_DECL_PUTBUF_)
+
+/* --- @buf_getdstr{8,{16,24,32,64}{,l,b},z} --- *
+ *
+ * Arguments:  @buf *b@ = pointer to a buffer block
+ *             @dstr *d@ = where to put the result
+ *
+ * Returns:    Zero if it worked, nonzero if there wasn't enough space.
+ *
+ * Use:                Gets a block of data from a buffer, and writes its contents
+ *             to a string.
+ */
+
+#define BUF_DECL_GETDSTR_(n, W, w)                                     \
+  extern int buf_getdstr##w(buf */*b*/, dstr */*d*/);
+BUF_DOSUFFIXES(BUF_DECL_GETDSTR_)
+
+/* --- @buf_putdstr{8,{16,24,32,64}{,l,b},z} --- *
+ *
+ * Arguments:  @buf *b@ = pointer to a buffer block
+ *             @dstr *d@ = string to write
+ *
+ * Returns:    Zero if it worked, nonzero if there wasn't enough space.
+ *
+ * Use:                Puts a dynamic string to a buffer.
+ */
+
+#define BUF_DECL_PUTDSTR_(n, W, w)                                     \
+  extern int buf_putdstr##w(buf */*b*/, dstr */*d*/);
+BUF_DOSUFFIXES(BUF_DECL_PUTDSTR_)
+
+/* --- @buf_putstr{8,{16,24,32,64}{,l,b},z} --- *
+ *
+ * Arguments:  @buf *b@ = pointer to a buffer block
+ *             @const char *p@ = string to write
+ *
+ * Returns:    Zero if it worked, nonzero if there wasn't enough space.
+ *
+ * Use:                Puts a null-terminated string to a buffer.
+ */
+
+#define BUF_DECL_PUTSTR_(n, W, w)                                      \
+  extern int buf_putstr##w(buf */*b*/, const char */*p*/);
+BUF_DOSUFFIXES(BUF_DECL_PUTSTR_)
+
+/*----- That's all, folks -------------------------------------------------*/
+
+#ifdef __cplusplus
+  }
+#endif
+
+#endif
diff --git a/struct/darray.3 b/struct/darray.3
new file mode 100644 (file)
index 0000000..8f7953c
--- /dev/null
@@ -0,0 +1,455 @@
+.\" -*-nroff-*-
+.de VS
+.sp 1
+.RS
+.nf
+.ft B
+..
+.de VE
+.ft R
+.fi
+.RE
+.sp 1
+..
+.de hP
+.IP
+.ft B
+\h'-\w'\\$1\ 'u'\\$1\ \c
+.ft P
+..
+.ie t .ds o \(bu
+.el .ds o o
+.TH darray 3 "21 October 1999" "Straylight/Edgeware" "mLib utilities library"
+.SH "NAME"
+darray \- dense, dynamically resizing arrays
+.\" @DA_INIT
+.\" @DA_DECL
+.\" @DA_CREATE
+.\" @DA_DESTROY
+.\" @DA_ENSURE
+.\" @DA_SHUNT
+.\" @DA_TIDY
+.\" @DA_RESET
+.\" @DA
+.\" @DA_LEN
+.\" @DA_SPARE
+.\" @DA_OFFSET
+.\" @DA_INCLUDE
+.\" @DA_EXTEND
+.\" @DA_UNSAFE_EXTEND
+.\" @DA_SLIDE
+.\" @DA_UNSAFE_SLIDE
+.\" @DA_SHRINK
+.\" @DA_UNSAFE_SHRINK
+.\" @DA_UNSLIDE
+.\" @DA_UNSAFE_UNSLIDE
+.\" @DA_FIRST
+.\" @DA_LAST
+.\" @DA_PUSH
+.\" @DA_POP
+.\" @DA_UNSHIFT
+.\" @DA_SHIFT
+.\" @DAEXC_UFLOW
+.\" @DAEXC_OFLOW
+.\" @da_ensure
+.\" @da_shunt
+.\" @da_tidy
+.SH "SYNOPSIS"
+.nf
+.B "#include <mLib/darray.h>"
+
+.BI DA_DECL( type_v ", " type );
+.IB type_v " " a " = DA_INIT;"
+.BI "void DA_CREATE(" type_v " *" a );
+.BI "void DA_DESTROY(" type_v " *" a );
+
+.BI "void DA_ENSURE(" type_v " *" a ", size_t " n );
+.BI "void DA_SHUNT(" type_v " *" a ", size_t " n );
+.BI "void DA_TIDY(" type_v " *" a );
+.BI "void DA_RESET(" type_v " *" a );
+
+.IB type " *DA(" type_v " *" a );
+.BI "size_t DA_LEN(" type_v " *" a );
+.BI "size_t DA_SPARE(" type_v " *" a );
+.BI "size_t DA_OFFSET(" type_v " *" a );
+.BI "void DA_INCLUDE(" type_v " *" a ", size_t " i );
+
+.BI "void DA_EXTEND(" type_v " *" a ", long " n );
+.BI "void DA_SHRINK(" type_v " *" a ", long " n );
+.BI "void DA_SLIDE(" type_v " *" a ", long " n );
+.BI "void DA_UNSLIDE(" type_v " *" a ", long " n );
+
+.BI "void DA_UNSAFE_EXTEND(" type_v " *" a ", long " n );
+.BI "void DA_UNSAFE_SHRINK(" type_v " *" a ", long " n );
+.BI "void DA_UNSAFE_SLIDE(" type_v " *" a ", long " n );
+.BI "void DA_UNSAFE_UNSLIDE(" type_v " *" a ", long " n );
+
+.IB type " DA_FIRST(" type_v " *" a );
+.IB type " DA_LAST(" type_v " *" a );
+.BI "void DA_PUSH(" type_v " *" a ", " type " " x );
+.IB type " DA_POP(" type_v " *" a );
+.BI "void DA_UNSHIFT(" type_v " *" a ", " type " " x );
+.IB type " DA_SHIFT(" type_v " *" a );
+
+.BI "void *da_ensure(da_base *" b ", void *" v ", size_t " sz ", size_t " n );
+.BI "void *da_shunt(da_base *" b ", void *" v ", size_t " sz ", size_t " n );
+.BI "void *da_tidy(da_base *" b ", void *" v ", size_t " sz );
+.fi
+.SH "DESCRIPTION"
+The
+.B <mLib/darray.h>
+header file declares a collection of types, macros and functions which
+implement dynamically resizing arrays.
+.PP
+The macros described here may evaluate their arguments multiple times
+unless otherwise specified.
+.SS "Creation and destruction"
+Each element type must have its own array
+type declared using the
+.B DA_DECL
+macro.  Calling
+.VS
+.BI DA_DECL( type_v ", " type );
+.VE
+Declares a new dynamic array type
+.I type_v
+whose elements have type
+.IR type .
+.PP
+It is conventional to form the name of an array type by appending
+.RB ` _v '
+to the base type name.  If the base type is a standard type, or is
+declared separately from the array, it's also conventional to declare a
+macro with the same name as the array type only in uppercase which may
+be used to prevent multiple declarations, e.g.,
+.VS
+#ifndef FOO_V
+#  define FOO_V
+   DA_DECL(foo_v, foo);
+#endif
+.VE
+The macro
+.B DA_INIT
+is a valid static initializer for all types of dynamic arrays.  For
+cases where this isn't appropriate, a dynamic array may be initialized
+using the macro
+.BR DA_CREATE ,
+passing it the address of the array.
+.PP
+Arrays may be disposed of using the
+.B DA_DESTROY
+macro, which again takes the address of the array.
+.SS "Storage allocation"
+.PP
+Space for new array elements may be reserved using the
+.B DA_ENSURE
+and
+.B DA_SHUNT
+macros, which reserve space at the end and beginning of the array
+respectively.  Both macros take two arguments: the address of an array
+object and the number of spare elements required.
+.PP
+Neither of these macros actually extends the array; both merely allocate
+memory for the array to extend itself into.  Use the macros
+.B DA_EXTEND
+and
+.B DA_SLIDE
+to actually increase the bounds of the array.
+.PP
+Note that when space is reserved, all the array elements might move.
+You should be careful not to depend on the addresses of array elements.
+If sufficient storage cannot be allocated, the exception
+.B EXC_NOMEM
+is thrown (see
+.BR exc (3)).
+.PP
+The macro
+.B DA_TIDY
+takes one argument: the address of a dynamic array.  It minimizes the
+amount of memory used by the array.  This is a useful function to call
+when the array's size has finally settled down.
+.PP
+The macro
+.B DA_RESET
+accepts the address of an array.  It reduces the length of the array to
+zero.  No storage is deallocated.  Resetting arrays might not be a good
+idea if the objects in the array are dynamically allocated.
+.SS "Accessing array elements"
+If
+.I a
+is the address of a dynamic array object, then
+.BI DA( a )
+is the base address of the actual array.  The elements are stored
+contiguously starting at this address.  An element at index
+.I i
+may be referenced using the syntax
+.BI DA( a )[ i ]\fR.
+.PP
+The number of elements in the array
+.I a
+is given by
+.BI DA_LEN( a )\fR.
+An integer array index
+.I i
+is
+.I valid
+if 0 \(<=
+.I i
+<
+.BI DA_LEN( a )\fR.
+.PP
+There may be some spare slots at the end of the array.  In particular,
+after a call to
+.BI DA_ENSURE( a ", " n )
+there will be at least
+.I n
+spare slots.  The number of spare slots at the end of the array may be
+obtained as
+.BI DA_SPARE( a )\fR.
+.PP
+Similarly, there may be spare slots before the start of the array.  In
+particular, after a call to
+.BI DA_SHUNT( a ", " n )
+there will be at least
+.I n
+spare slots.  The number of spare slots before the start of the array
+may be obtained as
+.BI DA_OFFSET( a )\fR.
+.PP
+The call
+.BI DA_INCLUDE( a ", " i )
+ensures that the array's bounds include the index
+.I i
+by extending the array if necessary.  The exception
+.B EXC_NOMEM
+is thrown if there isn't enough memory to do this.
+.PP
+The array's bounds may be extended by
+.I n
+slots by calling
+.BI DA_EXTEND( a ", " n )\fR.
+The integer
+.I n
+must be less than
+.BI DA_SPARE( a )\fR;
+if this is not the case then the exception
+.B DAEXC_OFLOW
+is thrown.
+Note that
+.I n
+may be negative to reduce the bounds of the array: in this case it must
+be greater than
+.BI \-DA_LEN( a )
+or the exception
+.B DAEXC_UFLOW
+is thrown.  The macro
+.B DA_UNSAFE_EXTEND
+does the same job, but performs no error checking.
+.PP
+The macro
+.BI DA_SLIDE( a ", " n )
+offsets the array elements by
+.IR n .
+If
+.I n
+is positive, the array elements are slid upwards, to higher-numbered
+indices; if
+.I n
+is negative, the elements are slid downwards.  Precisely, what happens
+is that elements which used to have index
+.I i
+\-
+.I n
+now have index
+.IR i .
+The exception
+.B DAEXC_OFLOW
+is thrown if
+.I n
+>
+.BI DA_OFFSET( a )\fR;
+.B DAEXC_UFLOW
+is thrown if
+.I n
+<
+.BI \-DA_LEN( a )\fR.
+The macro
+.B DA_UNSAFE_SLIDE
+does the same job, only without the error checking.
+.PP
+The macros
+.B DA_SHRINK
+and
+.B DA_UNSLIDE
+do the same things as
+.B DA_EXTEND
+and
+.B DA_SLIDE
+respectively, except that they interpret the sign of their second
+arguments in the opposite way.  This is useful if the argument is
+unsigned (e.g., if it's based on
+.BR DA_LEN ).
+There are unsafe versions of both these macros too.
+.SS "Stack operations"
+Dynamic arrays support Perl-like stack operations.  Given an array
+(pointer)
+.I a
+and an object of the array's element type
+.I x
+the following operations are provided:
+.TP
+.BI DA_PUSH( a ", " x )
+Add
+.I x
+to the end of the array, increasing the array size by one.
+.TP
+.IB x " = DA_POP(" a )
+Remove the final element of the array, assigning
+.I x
+its value and decreasing the array size by one.
+.TP
+.BI DA_UNSHIFT( a ", " x )
+Insert
+.I x
+at the beginning of the array, shifting all the elements up one place
+and increasing the array size by one.
+.TP
+.IB x " = DA_SHIFT(" a )
+Remove the first element of the array, assigning
+.I x
+its value, shifting all the subsequent array items down one place and
+decreasing the array size by one.
+.PP
+The operations
+.B DA_PUSH
+and
+.B DA_UNSHIFT
+can fail due to lack of memory, in which case
+.B EXC_NOMEM
+is thrown.  The operations
+.B DA_POP
+and
+.B DA_SHIFT
+can fail because the array is empty, in which case
+.B DAEXC_UFLOW
+is thrown.
+.PP
+The operations
+.B DA_FIRST
+and
+.B DA_LAST
+are lvalues referring to the first and last elements in the array
+respectively.  They are unsafe if the array is empty.
+.SS "Low-level details"
+This section describes low-level details of the dynamic array
+implementation.  You should try to avoid making use of this information
+if you can, since the interface may change in later library versions.
+In particular, more subtleties may be added which low-level access will
+miss.
+.PP
+Dynamic arrays are structures with the format
+.VS
+.BI "typedef struct " type_v " {"
+.B "  da_base b;"
+.BI "  " type " *v;"
+.BI "} " type_v ";"
+.VE
+The pointer
+.B v
+indicates the current base of the array.  This will move in the
+allocated space as a result of
+.B DA_SHIFT
+and
+.B DA_UNSHIFT
+(and
+.BR DA_SLIDE )
+operations.
+.PP
+The
+.B da_base
+structure contains the following elements:
+.TP
+.B "size_t sz"
+The number of allocated slots from
+.B v
+onwards.
+.TP
+.B "size_t len"
+The number of items considered to be in the array.  The allocated space
+is usually larger than this.
+.TP
+.B "size_t off"
+The number of allocated slots preceding
+.BR v .
+The total number of allocated items is therefore
+.B sz
++
+.BR off .
+.TP
+.B "unsigned push"
+The number of items pushed or ensured since the last array expansion.
+.TP
+.B "unsigned unshift"
+The number of items unshifted or shunted since the last array expansion.
+.PP
+The
+.B push
+and
+.B unshift
+members are used by the expansion routines to decide how to allocate
+free space before and after the array elements following a reallocation.
+The other members should be fairly self-explanatory.
+.PP
+The reallocation routines
+.BR da_ensure ,
+.B da_shunt
+and
+.B da_tidy
+have a regular interface.  They're a bit
+strange, though, because they have to deal with lots of different types
+of arrays.  The arguments they take are:
+.TP
+.BI "da_base *" b
+Pointer to the
+.B da_base
+member of the array block.
+.TP
+.BI "void *" v
+The array base pointer from the array block (i.e., the
+.B v
+member).
+.TP
+.BI "size_t " sz
+The element size for the array.
+.TP
+.BI "size_t " n
+(For
+.B da_ensure
+and
+.B da_shunt
+only.)  The number of spare slots required.
+.PP
+The functions may modify the base structure, and return a newly
+allocated (or at least repositioned) array base pointer, or throw
+.B EXC_NOMEM
+if there's not enough memory.
+.PP
+The three functions behave as follows:
+.TP
+.B da_ensure
+Ensure that there are at least
+.I n
+spare slots after the end of the array.
+.TP
+.B da_shunt
+Ensure that there are at least
+.I n
+spare slots preceding the start of the array.
+.TP
+.B da_tidy
+Reallocate the array to use the smallest amount of memory possible.
+.SH "SEE ALSO"
+.BR exc (3),
+.BR mLib (3).
+.SH "AUTHOR"
+Mark Wooding, <mdw@distorted.org.uk>
diff --git a/struct/darray.c b/struct/darray.c
new file mode 100644 (file)
index 0000000..aa7e4a2
--- /dev/null
@@ -0,0 +1,297 @@
+/* -*-c-*-
+ *
+ * Dynamically growing dense arrays
+ *
+ * (c) 1999 Straylight/Edgeware
+ */
+
+/*----- Licensing notice --------------------------------------------------*
+ *
+ * This file is part of the mLib utilities library.
+ *
+ * mLib is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * mLib is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with mLib; if not, write to the Free
+ * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+ * MA 02111-1307, USA.
+ */
+
+/*----- Header files ------------------------------------------------------*/
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+
+#include "alloc.h"
+#include "arena.h"
+#include "darray.h"
+
+/*----- Magic numbers -----------------------------------------------------*/
+
+#define DA_INITSZ 16                   /* Default size for new array */
+#define DA_SLOTS 8                     /* Number of preshifted slots */
+
+/*----- Main code ---------------------------------------------------------*/
+
+/* --- @da_ensure@ --- *
+ *
+ * Arguments:  @da_base *b@ = pointer to array base structure
+ *             @void *v@ = pointer to array vector
+ *             @size_t sz@ = size of individual array elements
+ *             @size_t n@ = number of items required at the end
+ *
+ * Returns:    Pointer to newly allocated or adjusted array vector.
+ *
+ * Use:                Extends a dynamic array to accommodate a number of new items
+ *             at its end.  This function is a helper for the @DA_ENSURE@
+ *             macro, which should be used by preference.
+ */
+
+void *da_ensure(da_base *b, void *v, size_t sz, size_t n)
+{
+  size_t rq = n + b->len;
+  char *p = v, *q;
+  size_t nsz;
+  size_t slots;
+
+  /* --- Make sure there's something which needs doing --- *
+   *
+   * If there's enough space already then return immediately.
+   */
+
+  if (rq < b->sz)
+    return (p);
+
+  /* --- Compute a number of `unshift' slots --- *
+   *
+   * When returning from this function, the offset will be set to @slots@.
+   * If @unshift@ is zero, there's no point in reserving slots.  Otherwise
+   * choose a power of two greater than @unshift@, with a minimum of
+   * @DA_SLOTS@.  Then add the number of slots to the requirement.
+   */
+
+  if (!b->unshift)
+    slots = 0;
+  else {
+    slots = DA_SLOTS;
+    while (slots < b->unshift)
+      slots <<= 1;
+  }
+  rq += slots;
+
+  /* --- Maybe just shunt data around a bit --- *
+   *
+   * If the vector is large enough, then theoretically we could cope by
+   * moving the objects about in their existing storage.  It's not worth
+   * bothering if there's not actually double the amount of space I need.
+   */
+
+  if (rq * 2 < b->sz + b->off) {
+    q = p - (b->off - slots) * sz;
+    memmove(q, p, b->len * sz);
+    b->sz += b->off - slots;
+    b->off = slots;
+    b->unshift = b->push = 0;
+    return (q);
+  }
+
+  /* --- Decide on a new size --- *
+   *
+   * There's a minimum possible size for the array which is used if it's
+   * currently completely empty.  Otherwise I choose the smallest power of
+   * two which is big enough, starting at double the current size.
+   */
+
+  nsz = v ? b->sz + b->off : (DA_INITSZ >> 1);
+  do nsz <<= 1; while (nsz < rq);
+
+  /* --- Reallocate the block --- *
+   *
+   * If I'm not changing the base offset then it's worth using @realloc@;
+   * otherwise there'll probably be two calls to @memcpy@ to shunt the data
+   * around so it's not worth bothering.
+   */
+
+  if (p && slots == b->off) {
+    q = x_realloc(b->a, p - b->off * sz, nsz * sz, b->sz + b->off);
+    q += slots * sz;
+  } else {
+    q = x_alloc(b->a, nsz * sz);
+    q += slots * sz;
+    if (p) {
+      memcpy(q, p, b->len * sz);
+      x_free(b->a, p - b->off * sz);
+    }
+  }
+
+  /* --- Fill in the other parts of the base structure --- */
+
+  b->off = slots;
+  b->sz = nsz - slots;
+  b->unshift = b->push = 0;
+  return (q);
+}
+
+/* --- @da_shunt@ --- *
+ *
+ * Arguments:  @da_base *b@ = pointer to array base structure
+ *             @void *v@ = pointer to array vector
+ *             @size_t sz@ = size of the array elements
+ *             @size_t n@ = number of items required at the start
+ *
+ * Returns:    Pointer to appropriately bodged vector.
+ *
+ * Use:                Extends an array to accommodate items inserted at its front.
+ *             This function is a helper for the @DA_SHUNT@ macro, which
+ *             should be used by preference.
+ */
+
+void *da_shunt(da_base *b, void *v, size_t sz, size_t n)
+{
+  size_t rq;
+  char *p = v, *q;
+  size_t nsz;
+  size_t slots;
+
+  /* --- Make sure there's something which needs doing --- *
+   *
+   * If there's enough space already then return immediately.
+   */
+
+  if (n < b->off)
+    return (p);
+
+  /* --- Compute a number of `push' slots --- *
+   *
+   * When returning from this function, there will be @slots@ free spaces at
+   * the end of the array.  If @push@ is zero, there's no point in reserving
+   * slots.  Otherwise choose a power of two greater than @push@, with a
+   * minimum of @DA_SLOTS@.  To simplify matters, add the number of items
+   * already in the array to @slots@, and then add the number of slots to the
+   * requirement.
+   */
+
+  if (!b->push)
+    slots = 0;
+  else {
+    slots = DA_SLOTS;
+    while (slots < b->push)
+      slots <<= 1;
+  }
+  slots += b->len;
+  rq = n + slots;
+
+  /* --- Maybe just shunt data around a bit --- *
+   *
+   * If the vector is large enough, then theoretically we could cope by
+   * moving the objects about in their existing storage.  Again, if there's
+   * not actually twice the space needed, reallocate the array.
+   */
+
+  if (rq * 2 < b->sz + b->off) {
+    q = p + (b->sz - slots) * sz;
+    memmove(q, p, b->len * sz);
+    b->off += b->sz - slots;
+    b->sz = slots;
+    b->unshift = b->push = 0;
+    return (q);
+  }
+
+  /* --- Reallocate the array --- *
+   *
+   * The neat @realloc@ code doesn't need to be here: the offset changes
+   * almost all the time -- that's the whole point of this routine!
+   */
+
+  /* --- Decide on a new size --- *
+   *
+   * There's a minimum possible size for the array which is used if it's
+   * currently completely empty.  Otherwise I choose the smallest power of
+   * two which is big enough, starting at double the current size.
+   */
+
+  nsz = v ? b->sz + b->off : (DA_INITSZ >> 1);
+  do nsz <<= 1; while (nsz < rq);
+
+  /* --- Reallocate the block --- *
+   *
+   * The neat @realloc@ code doesn't need to be here: the offset changes
+   * almost all the time -- that's the whole point of this routine!
+   */
+
+  q = x_alloc(b->a, nsz * sz);
+  q += (nsz - slots) * sz;
+  if (p) {
+    memcpy(q, p, b->len * sz);
+    x_free(b->a, p - b->off * sz);
+  }
+
+  /* --- Fill in the other parts of the base structure --- */
+
+  b->off = nsz - slots;
+  b->sz = slots;
+  b->unshift = b->push = 0;
+  return (q);
+}
+
+/* --- @da_tidy@ --- *
+ *
+ * Arguments:  @da_base *b@ = pointer to array base structure
+ *             @void *v@ = pointer to vector
+ *             @size_t sz@ = size of the array elements
+ *
+ * Returns:    Newly allocated vector.
+ *
+ * Use:                Minimizes the space occupied by an array.  This function is a
+ *             helper for the @DA_TIDY@ macro, which should be used by
+ *             preference.
+ */
+
+void *da_tidy(da_base *b, void *v, size_t sz)
+{
+  char *p = v, *q;
+
+  b->unshift = b->push = 0;
+
+  if (!p)
+    return (0);
+  if (b->sz == b->len && b->off == 0)
+    return (p);
+
+  if (!b->len) {
+    xfree(p - b->off * sz);
+    return (0);
+  }
+
+  q = x_alloc(b->a, b->len * sz);
+  memcpy(q, p, b->len * sz);
+  x_free(b->a, p - b->off * sz);
+  b->sz = b->len;
+  b->off = 0;
+  return (q);
+}
+
+/* --- Note about testing --- *
+ *
+ * The test rig for this code is split into three parts.  There's `da-gtest',
+ * which is a Perl script which generates a list of commands.  The `da-ref'
+ * Perl script interprets these commands as operations on a Perl array.  It's
+ * relatively conservatively written and believed to be reliable.  The
+ * `da-test.c' file implements a command reader for the same syntax and
+ * performs the operations on an integer darray, producing output in the same
+ * format.  To test darray, generate a command script with `da-gtest', pass
+ * it through both `da-ref' and `da-test' (the result of compiling
+ * da-test.c'), and compare the results.  If they're not byte-for-byte
+ * identical, there's something wrong.
+ */
+
+/*----- That's all, folks -------------------------------------------------*/
diff --git a/struct/darray.h b/struct/darray.h
new file mode 100644 (file)
index 0000000..9e07929
--- /dev/null
@@ -0,0 +1,526 @@
+/* -*-c-*-
+ *
+ * Dynamically growing dense arrays
+ *
+ * (c) 1999 Straylight/Edgeware
+ */
+
+/*----- Licensing notice --------------------------------------------------*
+ *
+ * This file is part of the mLib utilities library.
+ *
+ * mLib is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * mLib is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with mLib; if not, write to the Free
+ * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+ * MA 02111-1307, USA.
+ */
+
+#ifndef MLIB_DARRAY_H
+#define MLIB_DARRAY_H
+
+#ifdef __cplusplus
+  extern "C" {
+#endif
+
+/*----- Header files ------------------------------------------------------*/
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#ifndef MLIB_ALLOC_H
+#  include "alloc.h"
+#endif
+
+#ifndef MLIB_EXC_H
+#  include "exc.h"
+#endif
+
+/*----- Various important constants ---------------------------------------*/
+
+/* --- @DAEXC_UFLOW@, @DAEXC_OFLOW@ --- *
+ *
+ * Underflow and overflow exceptions raised by @DA_SHIFT@, and by @DA_POP@
+ * and @DA_SHIFT@.
+ */
+
+#define DAEXC_UFLOW EXC_ALLOCN(EXC_MLIB, 0)
+#define DAEXC_OFLOW EXC_ALLOCN(EXC_MLIB, 1)
+
+/*----- Data structures ---------------------------------------------------*/
+
+/* --- Base structure for dynamic arrays --- *
+ *
+ * An actual array has a `vector' @v@ in addition to this data (which is
+ * tucked away in the @b@ member).  The vector contains the actual storage
+ * for the array elements.
+ *
+ * The vector pointer @v@ potentially points somewhere in the middle of the
+ * allocated storage.  The @off@ member explains how far into the storage the
+ * vector points.  The allocated storage is sufficient for @sz + off@ items
+ * to be stored.  Valid array indices are integers between 0 (inclusive) and
+ * @len@ (exclusive).  Thus, from @v@ onwards, there is space for @sz@
+ * elements, and of these, @sz - len@ are currently not considered to be
+ * within the array's bounds.
+ *
+ * The @push@ and @unshift@ counts record how many times these operations
+ * have been performed since the last extension of the array.  They are used
+ * by the extension algorithm to decide how to position the data offset.
+ *
+ * Try to use the access macros rather than the structure members.
+ */
+
+typedef struct da_base {
+  size_t sz;                           /* Size of allocated vector */
+  size_t len;                          /* Length of useful portion */
+  size_t off;                          /* Offset of @v@ into space */
+  unsigned push, unshift;              /* Pushes/unshifts since growth */
+  arena *a;                            /* Pointer to allocation arena */
+} da_base;
+
+/* --- @DA_DECL@ --- *
+ *
+ * Arguments:  @atype@ = type name for the array
+ *             @type@ = item type for the array
+ *
+ * Use:                Declares a structure for decribing a dynamic array.
+ */
+
+#define DA_DECL(type_v, type)                                          \
+  typedef struct type_v { da_base b; type *v; } type_v
+
+/*----- Initialization, creation and destruction --------------------------*/
+
+#define DA_INIT { { 0, 0, 0, 0, 0, &arena_stdlib }, 0 }
+
+/* --- @DA_CREATE@ --- *
+ *
+ * Arguments:  @a@ = pointer to an array block (multiply evaluated)
+ *
+ * Use:                Initializes an array block.
+ */
+
+#define DA_CREATE(aa) do {                                             \
+  (aa)->b.sz = (aa)->b.len = 0;                                                \
+  (aa)->b.off = 0;                                                     \
+  (aa)->b.push = (aa)->b.unshift = 0;                                  \
+  (aa)->b.a = &arena_stdlib;                                           \
+  (aa)->v = 0;                                                         \
+} while (0)
+
+/* --- @DA_DESTROY@ --- *
+ *
+ * Arguments:  @a@ = pointer to an array block (multiply evaluated)
+ *
+ * Use:                Destroys an array.  The array is left valid but empty.
+ */
+
+#define DA_DESTROY(aa) do {                                            \
+  if ((aa)->v)                                                         \
+    x_free((aa)->b.a, (aa)->v - (aa)->b.off);                          \
+  DA_CREATE(aa);                                                               \
+} while (0)
+
+/*----- Storage reservation -----------------------------------------------*/
+
+/* --- @DA_ENSURE@ --- *
+ *
+ * Arguments:  @a@ = pointer to an array block (multiply evaluated)
+ *             @n@ = required number of spare items in the array
+ *
+ * Use:                Ensures that there are at least @n@ spare slots at the end of
+ *             the array.
+ */
+
+#define DA_ENSURE(a, n) do {                                           \
+  size_t _n = (n);                                                     \
+  if (_n > (a)->b.sz - (a)->b.len)                                     \
+    (a)->v = da_ensure(&(a)->b, (a)->v, sizeof((a)->v[0]), _n);                \
+  else                                                                 \
+    (a)->b.push += _n;                                                 \
+} while (0)
+
+/* --- @DA_SHUNT@ --- *
+ *
+ * Arguments:  @a@ = pointer to an array block (multiply evaluated)
+ *             @n@ = required number of spare items in the array
+ *
+ * Use:                Ensures that there are at least @n@ spare slots at the start
+ *             of the array.
+ */
+
+#define DA_SHUNT(a, n) do {                                            \
+  size_t _n = (n);                                                     \
+  if (_n > (a)->b.off)                                                 \
+    (a)->v = da_shunt(&(a)->b, (a)->v, sizeof((a)->v[0]), _n);         \
+  else                                                                 \
+    (a)->b.unshift += _n;                                              \
+} while (0)
+
+/* --- @DA_TIDY@ --- *
+ *
+ * Arguments:  @a@ = pointer to an array block (multiply evaluated)
+ *
+ * Use:                Reduces the amount of storage required by an array to its
+ *             minimum possible.
+ */
+
+#define DA_TIDY(a) ((a)->v = da_tidy(&(a)->b, (a)->v, sizeof((a)->v[0])))
+
+/* --- @DA_RESET@ --- *
+ *
+ * Arguments:  @a@ = pointer to array block
+ *
+ * Use:                Removes all the items from the named array.  This might not
+ *             be a good idea.  No storage is freed.
+ */
+
+#define DA_RESET(a) ((a)->b.len = 0)
+
+/*----- Access operations -------------------------------------------------*/
+
+/* --- @DA@ --- *
+ *
+ * Arguments:  @a@ = pointer to array block
+ *
+ * Use:                Expands to a reference to the array proper.  Given an array
+ *             @a@, item @i@ may be located using the expression @DA(a)[i]@.
+ */
+
+#define DA(a) ((a)->v + 0)
+
+/* --- @DA_LEN@ --- *
+ *
+ * Arguments:  @a@ = pointer to array block
+ *
+ * Use:                Expands to the number of elements in the array.  Elements are
+ *             assigned integer indices in the half-open interval
+ *             [0, @DA_LEN(a)@).  Don't change the length directly; use
+ *             @DA_EXTEND@ instead.
+ */
+
+#define DA_LEN(a) ((a)->b.len + 0)
+
+/* --- @DA_SPARE@ --- *
+ *
+ * Arguments:  @a@ = pointer to array block (multiply evaluated)
+ *
+ * Use:                Evaluates to the number of spare array elements above the
+ *             end of the array.
+ */
+
+#define DA_SPARE(a) ((a)->b.sz - (a)->b.len)
+
+/* --- @DA_INCLUDE@ --- *
+ *
+ * Arguments:  @a@ = pointer to array block (multiply evaluated)
+ *             @i@ = index into array
+ *
+ * Use:                Extends the array (if necessary) so that it includes the
+ *             index @i@.
+ */
+
+#define DA_INCLUDE(a, i) do {                                          \
+  size_t _i = (i);                                                     \
+  size_t _len = DA_LEN(a);                                             \
+  if (_i >= _len) {                                                    \
+    size_t _nn = _i - _len + 1;                                                \
+    DA_ENSURE(a, _nn);                                                 \
+    DA_UNSAFE_EXTEND(a, _nn);                                          \
+  }                                                                    \
+} while (0)
+
+/* --- @DA_OFFSET@ --- *
+ *
+ * Arguments:  @a@ = pointer to array block
+ *
+ * Use:                Evaluates to the number of spare elements before the
+ *             beginning of the array.  Don't modify the offset directly;
+ *             use @DA_SLIDE@ instead.
+ */
+
+#define DA_OFFSET(a) ((a)->b.off + 0)
+
+/* --- @DA_EXTEND@ --- *
+ *
+ * Arguments:  @a@ = pointer to array block (multiply evaluated)
+ *             @n@ = number of slots to add (multiply evaluated)
+ *
+ * Use:                Extends the end of the array by @n@ elements.  The exception
+ *             @DAEXC_OFLOW@ is thrown if there is not enough space for the
+ *             new elements (i.e., @n > DA_SPARE(a)@) -- call @DA_ENSURE@ to
+ *             prevent this from happening.  The integer @n@ may be
+ *             negative; @DAEXC_UFLOW@ is called if @n < DA_LEN(a)@.
+ */
+
+#define DA_EXTEND(a, n) do {                                           \
+  if ((n) > 0 && (n) > DA_SPARE(a))                                    \
+    THROW(DAEXC_OFLOW);                                                        \
+  else if ((n) < 0 && -(n) > DA_LEN(a))                                        \
+    THROW(DAEXC_UFLOW);                                                        \
+  DA_UNSAFE_EXTEND(a, n);                                              \
+} while (0)
+
+/* --- @DA_UNSAFE_EXTEND@ --- *
+ *
+ * Arguments:  @a@ = pointer to array block (multiply evaluated)
+ *             @n@ = number of slots to add (multiply evaluated)
+ *
+ * Use:                As for @DA_EXTEND@, only it doesn't check for errors.
+ */
+
+#define DA_UNSAFE_EXTEND(a, n) do {                                    \
+  (a)->b.len += (n);                                                   \
+} while (0)
+
+/* --- @DA_SLIDE@ --- *
+ *
+ * Arguments:  @a@ = pointer to array block (multiply evaluated)
+ *             @n@ = number of positions to slide the array (multiply
+ *                     evaluated)
+ *
+ *
+ * Use:                Slides the array elements by @n@ positions.  A positive @n@
+ *             slides upwards, introducing new elements at the bottom; a
+ *             negative @n@ slides downwards, removing low-numbered
+ *             elements.  Formally, what was at index @i - n@ before the
+ *             slide is moved to index @i@.  It is an error to slide by more
+ *             than @DA_OFFSET(a)@ or less than @-DA_LEN(a)@.  The exception
+ *             @DAEXC_OFLOW@ is raised in the former case, and @DAEXC_UFLOW@
+ *             in the latter.
+ */
+
+#define DA_SLIDE(a, n) do {                                            \
+  if ((n) > 0 && (n) > DA_OFFSET(a))                                   \
+    THROW(DAEXC_OFLOW);                                                        \
+  else if ((n) < 0 && -(n) > DA_LEN(a))                                        \
+    THROW(DAEXC_UFLOW);                                                        \
+  DA_UNSAFE_SLIDE((a), (n));                                           \
+} while (0)
+
+/* --- @DA_UNSAFE_SLIDE@ --- *
+ *
+ * Arguments:  @a@ = pointer to array block (multiply evaluated)
+ *             @n@ = number of positions to slide the array (multiply
+ *                     evaluated)
+ *
+ * Use:                As for @DA_SLIDE@, only it doesn't check for errors.
+ */
+
+#define DA_UNSAFE_SLIDE(a, n) do {                                     \
+  (a)->v -= (n);                                                       \
+  (a)->b.len += (n);                                                   \
+  (a)->b.sz += (n);                                                    \
+  (a)->b.off -= (n);                                                   \
+} while (0)
+
+/* --- @DA_SHRINK@ --- *
+ *
+ * Arguments:  @a@ = pointer to array block (multiply evaluated)
+ *             @n@ = number of slots to remove (multiply evaluated)
+ *
+ * Use:                As for @DA_EXTEND@, with the sense of the argument reversed.
+ */
+
+#define DA_SHRINK(a, n) do {                                           \
+  if ((n) > 0 && (n) > DA_LEN(a))                                      \
+    THROW(DAEXC_UFLOW);                                                        \
+  else if ((n) < 0 && -(n) > DA_SPARE(a))                              \
+    THROW(DAEXC_OFLOW);                                                        \
+  DA_UNSAFE_SHRINK(a, n);                                              \
+} while (0)
+
+/* --- @DA_UNSAFE_SHRINK@ --- *
+ *
+ * Arguments:  @a@ = pointer to array block (multiply evaluated)
+ *             @n@ = number of slots to add (multiply evaluated)
+ *
+ * Use:                As for @DA_SHRINK@, only it doesn't check for errors.
+ */
+
+#define DA_UNSAFE_SHRINK(a, n) do {                                    \
+  (a)->b.len -= (n);                                                   \
+} while (0)
+
+/* --- @DA_UNSLIDE@ --- *
+ *
+ * Arguments:  @a@ = pointer to array block (multiply evaluated)
+ *             @n@ = number of positions to slide the array (multiply
+ *                     evaluated)
+ *
+ *
+ * Use:                As for @DA_SLIDE@, only in the other direction.
+ */
+
+#define DA_UNSLIDE(a, n) do {                                          \
+  if ((n) > 0 && (n) > DA_LEN(a))                                      \
+    THROW(DAEXC_UFLOW);                                                        \
+  else if ((n) < 0 && -(n) > DA_OFFSET(a))                             \
+    THROW(DAEXC_OFLOW);                                                        \
+  DA_UNSAFE_UNSLIDE((a), (n));                                         \
+} while (0)
+
+/* --- @DA_UNSAFE_UNSLIDE@ --- *
+ *
+ * Arguments:  @a@ = pointer to array block (multiply evaluated)
+ *             @n@ = number of positions to slide the array (multiply
+ *                     evaluated)
+ *
+ * Use:                As for @DA_UNSLIDE@, only it doesn't check for errors.
+ */
+
+#define DA_UNSAFE_UNSLIDE(a, n) do {                                   \
+  (a)->v += (n);                                                       \
+  (a)->b.len -= (n);                                                   \
+  (a)->b.sz -= (n);                                                    \
+  (a)->b.off += (n);                                                   \
+} while (0)
+
+/*----- Stack-like operations ---------------------------------------------*/
+
+/* --- @DA_FIRST@ --- *
+ *
+ * Arguments:  @a@ = pointer to an array block (multiply evaluated)
+ *
+ * Use:                Evaluates to the initial element in array @a@.  It is unsafe
+ *             to do this if the array is empty.  The array is not changed.
+ */
+
+#define DA_FIRST(a) (DA(a)[0])
+
+/* --- @DA_LAST@ --- *
+ *
+ * Arguments:  @a@ = pointer to an array block (multiply evaluated)
+ *
+ * Use:                Evaluates to the final element in array @a@.  It is unsafe
+ *             to do this if the array is empty.  The array is not changed.
+ */
+
+#define DA_LAST(a) (DA(a)[(a)->b.len - 1])
+
+/* --- @DA_PUSH@ --- *
+ *
+ * Arguments:  @a@ = pointer to an array block (multiply evaluated)
+ *             @x@ = item to append to the end
+ *
+ * Use:                Appends @x@ as the final element in the array @a@.
+ */
+
+#define DA_PUSH(a, x) do {                                             \
+  DA_ENSURE(a, 1);                                                     \
+  DA(a)[(a)->b.len++] = x;                                             \
+} while (0)
+
+/* --- @DA_POP@ --- *
+ *
+ * Arguments:  @a@ = pointer to an array block (multiply evaluated)
+ *
+ * Use:                Evaluates to the final element in array @a@.  The element is
+ *             removed.  An exception @DAEXC_UFLOW@ is raised if there is
+ *             no item available to pop.
+ */
+
+#define DA_POP(a)                                                      \
+  ((a)->b.len ? ((void)0) : THROW(DAEXC_UFLOW),                                \
+   DA(a)[--(a)->b.len])
+
+/* --- @DA_UNSHIFT@ --- *
+ *
+ * Arguments:  @a@ = pointer to an array block (multiply evaluated)
+ *             @x@ = the new element to insert
+ *
+ * Use:                Inserts a new element at the beginning of an array.  This
+ *             might take a while.
+ */
+
+#define DA_UNSHIFT(a, x) do {                                          \
+  DA_SHUNT(a, 1);                                                      \
+  DA_UNSAFE_SLIDE(a, 1);                                               \
+  DA(a)[0] = x;                                                                \
+} while (0)
+
+/* --- @DA_SHIFT@ --- *
+ *
+ * Arguments:  @a@ = pointer to an array block (multiply evaluated)
+ *
+ * Use:                Evaluates to the initial element in array @a@.  The element
+ *             is removed, and all other elements are shifted down one
+ *             place.  The exception @DAEXC_UFLOW@ is raised if there is no
+ *             element to return.
+ */
+
+#define DA_SHIFT(a)                                                    \
+  ((a)->b.len ? ((void)0) : THROW(DAEXC_UFLOW),                                \
+   (a)->b.len--,                                                       \
+   (a)->b.sz--,                                                                \
+   (a)->b.off++,                                                       \
+   *(a)->v++)
+
+/*----- Functions provided ------------------------------------------------*/
+
+/* --- @da_ensure@ --- *
+ *
+ * Arguments:  @da_base *b@ = pointer to array base structure
+ *             @void *v@ = pointer to array vector
+ *             @size_t sz@ = size of individual array elements
+ *             @size_t n@ = number of items required at the end
+ *
+ * Returns:    Pointer to newly allocated or adjusted array vector.
+ *
+ * Use:                Extends a dynamic array to accommodate a number of new items
+ *             at its end.  This function is a helper for the @DA_ENSURE@
+ *             macro, which should be used by preference.
+ */
+
+extern void *da_ensure(da_base */*b*/, void */*v*/,
+                      size_t /*sz*/, size_t /*n*/);
+
+/* --- @da_shunt@ --- *
+ *
+ * Arguments:  @da_base *b@ = pointer to array base structure
+ *             @void *v@ = pointer to array vector
+ *             @size_t sz@ = size of the array elements
+ *             @size_t n@ = number of items required at the start
+ *
+ * Returns:    Pointer to appropriately bodged vector.
+ *
+ * Use:                Extends an array to accommodate items inserted at its front.
+ *             This function is a helper for the @DA_SHUNT@ macro, which
+ *             should be used by preference.
+ */
+
+extern void *da_shunt(da_base */*b*/, void */*v*/,
+                     size_t /*sz*/, size_t /*n*/);
+
+/* --- @da_tidy@ --- *
+ *
+ * Arguments:  @da_base *b@ = pointer to array base structure
+ *             @void *v@ = pointer to vector
+ *             @size_t sz@ = size of the array elements
+ *
+ * Returns:    Newly allocated vector.
+ *
+ * Use:                Minimizes the space occupied by an array.  This function is a
+ *             helper for the @DA_TIDY@ macro, which should be used by
+ *             preference.
+ */
+
+extern void *da_tidy(da_base */*b*/, void */*v*/, size_t /*sz*/);
+
+/*----- That's all, folks -------------------------------------------------*/
+
+#ifdef __cplusplus
+  }
+#endif
+
+#endif
diff --git a/struct/dspool.3 b/struct/dspool.3
new file mode 100644 (file)
index 0000000..59e91b4
--- /dev/null
@@ -0,0 +1,94 @@
+.\" -*-nroff-*-
+.de VS
+.sp 1
+.in +5n
+.ft B
+.nf
+..
+.de VE
+.ft R
+.in -5n
+.sp 1
+.fi
+..
+.TH dspool 3 "20 June 1999" "Straylight/Edgeware" "mLib utilities library"
+.SH NAME
+dspool \- pools of preallocated dynamic strings
+.\" @dspool_create
+.\" @dspool_destroy
+.\" @dspool_get
+.\" @dspool_put
+.\"
+.\" @DSGET
+.\" @DSPUT
+.\"
+.SH SYNOPSIS
+.nf
+.B "#include <mLib/dspool.h>"
+
+.BI "void dspool_create(dspool *" p ", size_t " isz );
+.BI "void dspool_destroy(dspool *" p );
+.BI "dstr *dspool_get(dspool *" p );
+.BI "void dspool_put(dspool *" p ", dstr *" d );
+
+.BI "void DSGET(dspool *" p ", " d );
+.BI "void DSPUT(dspool *" p ", dstr *" d );
+.fi
+.SH DESCRIPTION
+A dynamic string pool maintains a collection of `spare' dynamic
+strings.  Some pieces of code require high turnover of strings, and
+allocating and freeing them entails a large amount of overhead.  A
+dynamic string pool keeps a list of dynamic strings which have been
+allocated but are not currently in use.
+.PP
+A pool is created by the function
+.BR dspool_create .
+It is passed the address of a pool structure
+.I p
+and the initial size
+.I isz
+to allocate for new dynamic strings obtained from the pool.  A newly
+created pool contains no strings.  Once a pool is no longer required,
+the function
+.B dspool_destroy
+will release all the strings in the pool, such that the pool can safely
+be thrown away.
+.PP
+A string is obtained from a pool by calling
+.BR dspool_get .
+If the pool is empty, a new string is allocated; otherwise a string is
+chosen from those currently in the pool.
+.PP
+A string is returned to the pool by the
+.B dspool_put
+function.  It is passed the address of a pool and the address of a
+string to return.  The string must have been allocated from
+.I some
+dynamic string pool, although it's not actually necessary to return it
+to the pool from which it was allocated.
+.PP
+The macro call
+.VS
+DSGET(p, d);
+.VE
+is equivalent to the assignment
+.VS
+d = dspool_get(p);
+.VE
+(except that it's probably quicker).  The macro
+.B DSPUT
+is entirely equivalent to the function
+.B dspool_put
+except for improved performance.
+.SH CAVEATS
+The string pool allocator requires the suballocator (see
+.BR sub (3)
+for details).  You must ensure that
+.B sub_init
+is called before any strings are allocated from a string pool.
+.SH SEE ALSO
+.BR dstr (3),
+.BR sub (3),
+.BR mLib (3).
+.SH AUTHOR
+Mark Wooding, <mdw@distorted.org.uk>
diff --git a/struct/dspool.c b/struct/dspool.c
new file mode 100644 (file)
index 0000000..53e2032
--- /dev/null
@@ -0,0 +1,105 @@
+/* -*-c-*-
+ *
+ * Provide pools of strings
+ *
+ * (c) 1999 Straylight/Edgeware
+ */
+
+/*----- Licensing notice --------------------------------------------------*
+ *
+ * This file is part of the mLib utilities library.
+ *
+ * mLib is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * mLib is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with mLib; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+/*----- Header files ------------------------------------------------------*/
+
+#include <stdio.h>
+
+#include "dspool.h"
+#include "dstr.h"
+#include "sub.h"
+
+/*----- Main code ---------------------------------------------------------*/
+
+/* --- @dspool_create@ --- *
+ *
+ * Arguments:  @dspool *p@ = address of pool to create
+ *             @size_t isz@ = initial size of new strings
+ *
+ * Returns:    ---
+ *
+ * Use:                Initializes a dynamic string pool.
+ */
+
+void dspool_create(dspool *p, size_t isz)
+{
+  p->free = 0;
+  p->isz = isz;
+}
+
+/* --- @dspool_destroy@ --- *
+ *
+ * Arguments:  @dspool *p@ = pool to destroy
+ *
+ * Returns:    ---
+ *
+ * Use:                Releases all of the strings left in the pool.  Any strings
+ *             not put back into the pool aren't freed.  However, the pool
+ *             is still valid, and the active strings can be put back and
+ *             released later.
+ */
+
+void dspool_destroy(dspool *p)
+{
+  dspoolstr *s = p->free;
+  while (s) {
+    dspoolstr *n = s->next;
+    DDESTROY(&s->ds);
+    DESTROY(s);
+    s = n;
+  }
+  p->free = 0;
+}
+
+/* --- @dspool_get@ --- *
+ *
+ * Arguments:  @dspool *p@ = pointer to a string pool
+ *
+ * Returns:    Pointer to a dynamic string.
+ *
+ * Use:                Fetches a string from the pool.  The string has space for at
+ *             least @isz@ characters (where @isz@ is the size passed to
+ *             @dspool_create@ for the pool).
+ */
+
+dstr *dspool_get(dspool *p) { dstr *d; DSGET(p, d); return (d); }
+
+/* --- @dspool_put@ --- *
+ *
+ * Arguments:  @dspool *p@ = pointer to a string pool
+ *             @dstr *d@ = pointer to a dynamic string from a string pool
+ *
+ * Returns:    ---
+ *
+ * Use:                Releases a dynamic string back into a string pool.  It
+ *             doesn't have to be the same pool the string actually came
+ *             from, although it does have to have come from some string
+ *             pool.
+ */
+
+void dspool_put(dspool *p, dstr *d) { DSPUT(p, d); }
+
+/*----- That's all, folks -------------------------------------------------*/
diff --git a/struct/dspool.h b/struct/dspool.h
new file mode 100644 (file)
index 0000000..47ca6e2
--- /dev/null
@@ -0,0 +1,141 @@
+/* -*-c-*-
+ *
+ * Provide pools of strings
+ *
+ * (c) 1999 Straylight/Edgeware
+ */
+
+/*----- Licensing notice --------------------------------------------------*
+ *
+ * This file is part of the mLib utilities library.
+ *
+ * mLib is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * mLib is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with mLib; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef MLIB_DSPOOL_H
+#define MLIB_DSPOOL_H
+
+#ifdef __cplusplus
+  extern "C" {
+#endif
+
+/*----- Header files ------------------------------------------------------*/
+
+#ifndef MLIB_DSTR_H
+#  include "dstr.h"
+#endif
+
+#ifndef MLIB_SUB_H
+#  include "sub.h"
+#endif
+
+/*----- Data structures ---------------------------------------------------*/
+
+typedef struct dspoolstr {
+  dstr ds;
+  struct dspoolstr *next;
+} dspoolstr;
+
+typedef struct dspool {
+  dspoolstr *free;
+  size_t isz;
+} dspool;
+
+/*----- Functions provided ------------------------------------------------*/
+
+/* --- @dspool_create@ --- *
+ *
+ * Arguments:  @dspool *p@ = address of pool to create
+ *             @size_t isz@ = initial size of new strings
+ *
+ * Returns:    ---
+ *
+ * Use:                Initializes a dynamic string pool.
+ */
+
+extern void dspool_create(dspool */*p*/, size_t /*isz*/);
+
+/* --- @dspool_destroy@ --- *
+ *
+ * Arguments:  @dspool *p@ = pool to destroy
+ *
+ * Returns:    ---
+ *
+ * Use:                Releases all of the strings left in the pool.  Any strings
+ *             not put back into the pool aren't freed.  However, the pool
+ *             is still valid, and the active strings can be put back and
+ *             released later.
+ */
+
+extern void dspool_destroy(dspool */*p*/);
+
+/* --- @dspool_get@ --- *
+ *
+ * Arguments:  @dspool *p@ = pointer to a string pool
+ *
+ * Returns:    Pointer to a dynamic string.
+ *
+ * Use:                Fetches a string from the pool.  The string has space for at
+ *             least @isz@ characters (where @isz@ is the size passed to
+ *             @dspool_create@ for the pool).
+ */
+
+extern dstr *dspool_get(dspool */*p*/);
+
+#define DSGET(p, d) do {                                               \
+  dspoolstr *_s;                                                       \
+  dspool *_p = (p);                                                    \
+  if (_p->free) {                                                      \
+    _s = _p->free;                                                     \
+    _p->free = _s->next;                                               \
+  } else {                                                             \
+    _s = CREATE(dspoolstr);                                            \
+    DCREATE(&_s->ds);                                                  \
+    if (_p->isz)                                                       \
+      DENSURE(&_s->ds, _p->isz);                                       \
+  }                                                                    \
+  d = &_s->ds;                                                         \
+} while (0)
+
+/* --- @dspool_put@ --- *
+ *
+ * Arguments:  @dspool *p@ = pointer to a string pool
+ *             @dstr *d@ = pointer to a dynamic string from a string pool
+ *
+ * Returns:    ---
+ *
+ * Use:                Releases a dynamic string back into a string pool.  It
+ *             doesn't have to be the same pool the string actually came
+ *             from, although it does have to have come from some string
+ *             pool.
+ */
+
+extern void dspool_put(dspool */*p*/, dstr */*d*/);
+
+#define DSPUT(p, d) do {                                               \
+  dspool *_p = (p);                                                    \
+  dspoolstr *_s = (dspoolstr *)(d);                                    \
+  DRESET(d);                                                           \
+  _s->next = _p->free;                                                 \
+  _p->free = _s;                                                       \
+} while (0)
+
+/*----- That's all, folks -------------------------------------------------*/
+
+#ifdef __cplusplus
+  }
+#endif
+
+#endif
diff --git a/struct/dstr-putf.c b/struct/dstr-putf.c
new file mode 100644 (file)
index 0000000..270f9a3
--- /dev/null
@@ -0,0 +1,568 @@
+/* -*-c-*-
+ *
+ * `printf'-style formatting for dynamic strings
+ *
+ * (c) 1999 Straylight/Edgeware
+ */
+
+/*----- Licensing notice --------------------------------------------------*
+ *
+ * This file is part of the mLib utilities library.
+ *
+ * mLib is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * mLib is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with mLib; if not, write to the Free
+ * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+ * MA 02111-1307, USA.
+ */
+
+/*----- Header files ------------------------------------------------------*/
+
+#include "config.h"
+
+#include <assert.h>
+#include <ctype.h>
+#include <limits.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#ifdef HAVE_FLOAT_H
+#  include <float.h>
+#endif
+
+#ifdef HAVE_STDINT_H
+#  include <stdint.h>
+#endif
+
+#include "darray.h"
+#include "dstr.h"
+#include "macros.h"
+
+/*----- Tunable constants -------------------------------------------------*/
+
+/*
+ * For each format specifier, at least @PUTFSTEP@ bytes are ensured before
+ * writing the formatted result.
+ */
+
+#define PUTFSTEP 64                    /* Buffer size for @putf@ */
+
+/*----- Preliminary definitions -------------------------------------------*/
+
+#ifdef HAVE_FLOAT_H
+#  define IF_FLOAT(x) x
+#else
+#  define IF_FLOAT(x)
+#endif
+
+#if defined(LLONG_MAX) || defined(LONG_LONG_MAX)
+#  define IF_LONGLONG(x) x
+#else
+#  define IF_LONGLONG(x)
+#endif
+
+#ifdef INTMAX_MAX
+#  define IF_INTMAX(x) x
+#else
+#  define IF_INTMAX(x)
+#endif
+
+#define OUTPUT_FMTTYPES(_)                                             \
+  _(i, unsigned int)                                                   \
+  _(li, unsigned long)                                                 \
+  IF_LONGLONG( _(lli, unsigned long long) )                            \
+  _(zi, size_t)                                                                \
+  _(ti, ptrdiff_t)                                                     \
+  IF_INTMAX( _(ji, uintmax_t) )                                                \
+  _(s, char *)                                                         \
+  _(p, void *)                                                         \
+  _(f, double)                                                         \
+  _(Lf, long double)
+
+#define PERCENT_N_FMTTYPES(_)                                          \
+  _(n, int *)                                                          \
+  _(hhn, char *)                                                       \
+  _(hn, short *)                                                       \
+  _(ln, long *)                                                                \
+  _(zn, size_t *)                                                      \
+  _(tn, ptrdiff_t *)                                                   \
+  IF_LONGLONG( _(lln, long long *) )                                   \
+  IF_INTMAX( _(jn, intmax_t *) )
+
+#define FMTTYPES(_)                                                    \
+  OUTPUT_FMTTYPES(_)                                                   \
+  PERCENT_N_FMTTYPES(_)
+
+enum {
+  fmt_unset = 0,
+#define CODE(code, ty) fmt_##code,
+  FMTTYPES(CODE)
+#undef CODE
+  fmt__limit
+};
+
+typedef struct {
+  int fmt;
+  union {
+#define MEMB(code, ty) ty code;
+    FMTTYPES(MEMB)
+#undef MEMB
+  } u;
+} fmtarg;
+
+DA_DECL(fmtarg_v, fmtarg);
+
+enum {
+  len_std = 0,
+  len_hh,
+  len_h,
+  len_l,
+  len_ll,
+  len_z,
+  len_t,
+  len_j,
+  len_L
+};
+
+#define f_len          0x000fu
+#define f_wd           0x0010u
+#define f_wdarg                0x0020u
+#define f_prec         0x0040u
+#define f_precarg      0x0080u
+#define f_plus         0x0100u
+#define f_minus                0x0200u
+#define f_sharp                0x0400u
+#define f_zero         0x0800u
+#define f_posarg       0x1000u
+
+typedef struct {
+  const char *p;
+  size_t n;
+  unsigned f;
+  int fmt, ch;
+  int wd, prec;
+  int arg;
+} fmtspec;
+
+DA_DECL(fmtspec_v, fmtspec);
+
+/*----- Main code ---------------------------------------------------------*/
+
+/* --- @dstr_vputf@ --- *
+ *
+ * Arguments:  @dstr *d@ = pointer to a dynamic string block
+ *             @const char *p@ = pointer to @printf@-style format string
+ *             @va_list *ap@ = argument handle
+ *
+ * Returns:    The number of characters written to the string.
+ *
+ * Use:                As for @dstr_putf@, but may be used as a back-end to user-
+ *             supplied functions with @printf@-style interfaces.
+ */
+
+static void set_arg(fmtarg_v *av, size_t i, int fmt)
+{
+  size_t j, n;
+
+  n = DA_LEN(av);
+  if (i >= n) {
+    DA_ENSURE(av, i + 1 - n);
+    for (j = n; j <= i; j++) DA(av)[j].fmt = fmt_unset;
+    DA_UNSAFE_EXTEND(av, i + 1 - n);
+  }
+
+  if (DA(av)[i].fmt == fmt_unset) DA(av)[i].fmt = fmt;
+  else assert(DA(av)[i].fmt == fmt);
+}
+
+int dstr_vputf(dstr *d, const char *p, va_list *ap)
+{
+  size_t n = d->len;
+  size_t sz, mx;
+  dstr dd = DSTR_INIT;
+  fmtspec_v sv = DA_INIT;
+  fmtarg_v av = DA_INIT;
+  fmtarg *fa, *fal;
+  fmtspec *fs, *fsl;
+  unsigned f;
+  int i, anext;
+  int wd, prec;
+
+  /* --- Initial pass through the input, parsing format specifiers --- *
+   *
+   * We essentially compile the format string into a vector of @fmtspec@
+   * objects, each of which represents a chunk of literal text followed by a
+   * (possibly imaginary, in the case of the final one) formatting directive.
+   * Output then simply consists of interpreting these specifiers in order.
+   */
+
+  anext = 0;
+
+  while (*p) {
+    f = 0;
+    DA_ENSURE(&sv, 1);
+    fs = &DA(&sv)[DA_LEN(&sv)];
+    DA_UNSAFE_EXTEND(&sv, 1);
+
+    /* --- Find the end of this literal portion --- */
+
+    fs->p = p;
+    while (*p && *p != '%') p++;
+    fs->n = p - fs->p;
+
+    /* --- Some simple cases --- *
+     *
+     * We might have reached the end of the string, or maybe a `%%' escape.
+     */
+
+    if (!*p) { fs->fmt = fmt_unset; fs->ch = 0; break; }
+    p++;
+    if (*p == '%') { fs->fmt = fmt_unset; fs->ch = '%'; p++; continue; }
+
+    /* --- Pick up initial flags --- */
+
+  flags:
+    for (;;) {
+      switch (*p) {
+       case '+': f |= f_plus; break;
+       case '-': f |= f_minus; break;
+       case '#': f |= f_sharp; break;
+       case '0': f |= f_zero; break;
+       default: goto done_flags;
+      }
+      p++;
+    }
+
+    /* --- Pick up the field width --- */
+
+  done_flags:
+    i = 0;
+    while (ISDIGIT(*p)) i = 10*i + *p++ - '0';
+
+    /* --- Snag: this might have been an argument position indicator --- */
+
+    if (i && *p == '$' && (!f || f == f_zero)) {
+      f |= f_posarg;
+      fs->arg = i - 1;
+      p++;
+      goto flags;
+    }
+
+    /* --- Set the field width --- *
+     *
+     * If @i@ is nonzero here then we have a numeric field width.  Otherwise
+     * it might be `*', maybe with an explicit argument number.
+     */
+
+    if (i) {
+      f |= f_wd;
+      fs->wd = i;
+    } else if (*p == '*') {
+      p++;
+      if (!ISDIGIT(*p))
+       i = anext++;
+      else {
+       i = *p++ - '0';
+       while (ISDIGIT(*p)) i = 10*i + *p++ - '0';
+       assert(*p == '$'); p++;
+       assert(i > 0); i--;
+      }
+      f |= f_wd | f_wdarg;
+      set_arg(&av, i, fmt_i); fs->wd = i;
+    }
+
+    /* --- Maybe we have a precision spec --- */
+
+    if (*p == '.') {
+      p++;
+      f |= f_prec;
+      if (ISDIGIT(*p)) {
+       i = *p++ - '0';
+       while (ISDIGIT(*p)) i = 10*i + *p++ - '0';
+       fs->prec = i;
+      } else if (*p != '*')
+       fs->prec = 0;
+      else {
+       p++;
+       if (!ISDIGIT(*p))
+         i = anext++;
+       else {
+         i = *p++ - '0';
+         while (ISDIGIT(*p)) i = 10*i + *p++ - '0';
+         assert(*p == '$'); p++;
+         assert(i > 0); i--;
+       }
+       f |= f_precarg;
+       set_arg(&av, i, fmt_i); fs->prec = i;
+      }
+    }
+
+    /* --- Maybe some length flags --- */
+
+    switch (*p) {
+      case 'h':
+       p++;
+       if (*p == 'h') { f |= len_hh; p++; } else f |= len_h;
+       break;
+      case 'l':
+       p++;
+       IF_LONGLONG( if (*p == 'l') { f |= len_ll; p++; } else ) f |= len_l;
+       break;
+      case 'L': f |= len_L; p++; break;
+      case 'z': f |= len_z; p++; break;
+      case 't': f |= len_t; p++; break;
+      IF_INTMAX( case 'j': f |= len_j; p++; break; )
+    }
+
+    /* --- The flags are now ready --- */
+
+    fs->f = f;
+
+    /* --- At the end, an actual directive --- */
+
+    fs->ch = *p;
+    switch (*p++) {
+      case '%':
+       fs->fmt = fmt_unset;
+       break;
+      case 'd': case 'i': case 'x': case 'X': case 'o': case 'u':
+       switch (f & f_len) {
+         case len_l: fs->fmt = fmt_li; break;
+         case len_z: fs->fmt = fmt_zi; break;
+         case len_t: fs->fmt = fmt_ti; break;
+         IF_LONGLONG( case len_ll: fs->fmt = fmt_lli; break; )
+         IF_INTMAX( case len_j: fs->fmt = fmt_ji; break; )
+         default: fs->fmt = fmt_i;
+       }
+       break;
+      case 'a': case 'A':
+      case 'e': case 'E': case 'f': case 'F': case 'g': case 'G':
+       fs->fmt = (f & f_len) == len_L ? fmt_Lf : fmt_f;
+       break;
+      case 'c':
+       fs->fmt = fmt_i;
+       break;
+      case 's':
+       fs->fmt = fmt_s;
+       break;
+      case 'p':
+       fs->fmt = fmt_p;
+       break;
+      case 'n':
+       switch (f & f_len) {
+         case len_hh: fs->fmt = fmt_hhn; break;
+         case len_h: fs->fmt = fmt_hn; break;
+         case len_l: fs->fmt = fmt_ln; break;
+         case len_z: fs->fmt = fmt_zn; break;
+         case len_t: fs->fmt = fmt_tn; break;
+         IF_LONGLONG( case len_ll: fs->fmt = fmt_lln; break; )
+         IF_INTMAX( case len_j: fs->fmt = fmt_jn; break; )
+         default: fs->fmt = fmt_n;
+       }
+       break;
+      default:
+       fprintf(stderr,
+               "FATAL dstr_vputf: unknown format specifier `%c'\n", p[-1]);
+       abort();
+    }
+
+    /* --- Finally sort out the argument --- *
+     *
+     * If we don't have explicit argument positions then this comes after the
+     * width and precision; and we don't know the type code until we've
+     * parsed the specifier, so this seems the right place to handle it.
+     */
+
+    if (!(f & f_posarg)) fs->arg = anext++;
+    set_arg(&av, fs->arg, fs->fmt);
+  }
+
+  /* --- Quick pass over the argument vector to collect the arguments --- */
+
+  for (fa = DA(&av), fal = fa + DA_LEN(&av); fa < fal; fa++) {
+    switch (fa->fmt) {
+#define CASE(code, ty) case fmt_##code: fa->u.code = va_arg(*ap, ty); break;
+      FMTTYPES(CASE)
+#undef CASE
+      default: abort();
+    }
+  }
+
+  /* --- Final pass through the format string to produce output --- */
+
+  fa = DA(&av);
+  for (fs = DA(&sv), fsl = fs + DA_LEN(&sv); fs < fsl; fs++) {
+    f = fs->f;
+
+    /* --- Output the literal portion --- */
+
+    if (fs->n) DPUTM(d, fs->p, fs->n);
+
+    /* --- And now the variable portion --- */
+
+    if (fs->fmt == fmt_unset) {
+      switch (fs->ch) {
+       case 0: break;
+       case '%': DPUTC(d, '%'); break;
+       default: abort();
+      }
+      continue;
+    }
+
+    DRESET(&dd);
+    DPUTC(&dd, '%');
+
+    /* --- Resolve the width and precision --- */
+
+    if (!(f & f_wd))
+      wd = 0;
+    else {
+      wd = (fs->f & f_wdarg) ? *(int *)&fa[fs->wd].u.i : fs->wd;
+      if (wd < 0) { wd = -wd; f |= f_minus; }
+    }
+
+    if (!(f & f_prec))
+      prec = 0;
+    else {
+      prec = (fs->f & f_precarg) ? *(int *)&fa[fs->prec].u.i : fs->prec;
+      if (prec < 0) { prec = 0; f &= ~f_prec; }
+    }
+
+    /* --- Write out the flags, width and precision --- */
+
+    if (f & f_plus) DPUTC(&dd, '+');
+    if (f & f_minus) DPUTC(&dd, '-');
+    if (f & f_sharp) DPUTC(&dd, '#');
+    if (f & f_zero) DPUTC(&dd, '0');
+
+    if (f & f_wd) {
+      DENSURE(&dd, PUTFSTEP);
+      dd.len += sprintf(dd.buf + dd.len, "%d", wd);
+    }
+
+    if (f & f_prec) {
+      DENSURE(&dd, PUTFSTEP + 1);
+      dd.len += sprintf(dd.buf + dd.len, ".%d", prec);
+    }
+
+    /* --- Write out the length gadget --- */
+
+    switch (f & f_len) {
+      case len_hh: DPUTC(&dd, 'h'); /* fall through */
+      case len_h: DPUTC(&dd, 'h'); break;
+      IF_LONGLONG( case len_ll: DPUTC(&dd, 'l'); /* fall through */ )
+      case len_l: DPUTC(&dd, 'l'); break;
+      case len_z: DPUTC(&dd, 'z'); break;
+      case len_t: DPUTC(&dd, 't'); break;
+      case len_L: DPUTC(&dd, 'L'); break;
+      IF_INTMAX( case len_j: DPUTC(&dd, 'j'); break; )
+      case len_std: break;
+      default: abort();
+    }
+
+    /* --- And finally the actually important bit --- */
+
+    DPUTC(&dd, fs->ch);
+    DPUTZ(&dd);
+
+    /* --- Make sure we have enough space for the output --- */
+
+    sz = PUTFSTEP;
+    if (sz < wd) sz = wd;
+    if (sz < prec + 16) sz = prec + 16;
+    switch (fs->ch) {
+      case 'a': case 'A':
+      case 'e': case 'E': case 'f': case 'F': case 'g': case 'G':
+#ifdef HAVE_FLOAT_H
+       if (fs->ch == 'f') {
+         mx = ((fs->f & f_len) == len_L ?
+               LDBL_MAX_10_EXP : DBL_MAX_10_EXP) + 16;
+         if (sz < mx) sz = mx;
+       }
+       break;
+#else
+       DPUTS(d, "<no float support>");
+       continue;
+#endif
+      case 's':
+       if (!(f & f_prec)) {
+         n = strlen(fa[fs->arg].u.s);
+         if (sz < n) sz = n;
+       }
+       break;
+      case 'n':
+       switch (fs->fmt) {
+#define CASE(code, ty)                                                 \
+  case fmt_##code: *fa[fs->arg].u.code = d->len - n; break;
+         PERCENT_N_FMTTYPES(CASE)
+#undef CASE
+         default: abort();
+       }
+       continue;
+    }
+
+    /* --- Finally do the output stage --- */
+
+    DENSURE(d, sz + 1);
+    switch (fs->fmt) {
+#ifdef HAVE_SNPRINTF
+#  define CASE(code, ty) case fmt_##code:                              \
+     i = snprintf(d->buf + d->len, sz + 1, dd.buf, fa[fs->arg].u.code);        \
+     break;
+#else
+#  define CASE(code, ty) case fmt_##code:                              \
+     i = sprintf(d->buf + d->len, dd.buf, fa[fs->arg].u.code);         \
+     break;
+#endif
+      OUTPUT_FMTTYPES(CASE)
+#undef CASE
+      default: abort();
+    }
+    assert(0 <= i && i <= sz); d->len += i;
+  }
+
+  /* --- We're done --- */
+
+  DPUTZ(d);
+  DDESTROY(&dd);
+  DA_DESTROY(&av);
+  DA_DESTROY(&sv);
+  return (d->len - n);
+}
+
+/* --- @dstr_putf@ --- *
+ *
+ * Arguments:  @dstr *d@ = pointer to a dynamic string block
+ *             @const char *p@ = pointer to @printf@-style format string
+ *             @...@ = argument handle
+ *
+ * Returns:    The number of characters written to the string.
+ *
+ * Use:                Writes a piece of text to a dynamic string, doing @printf@-
+ *             style substitutions as it goes.  Intended to be robust if
+ *             faced with malicious arguments, but not if the format string
+ *             itself is malicious.
+ */
+
+int dstr_putf(dstr *d, const char *p, ...)
+{
+  int n;
+  va_list ap;
+  va_start(ap, p);
+  n = dstr_vputf(d, p, &ap);
+  va_end(ap);
+  return (n);
+}
+
+/*----- That's all, folks -------------------------------------------------*/
diff --git a/struct/dstr.3 b/struct/dstr.3
new file mode 100644 (file)
index 0000000..e59f370
--- /dev/null
@@ -0,0 +1,414 @@
+.\" -*-nroff-*-
+.de VS
+.sp 1
+.RS
+.nf
+.ft B
+..
+.de VE
+.ft R
+.fi
+.RE
+.sp 1
+..
+.de hP
+.IP
+.ft B
+\h'-\w'\\$1\ 'u'\\$1\ \c
+.ft P
+..
+.ie t .ds o \(bu
+.el .ds o o
+.TH dstr 3 "8 May 1999" "Straylight/Edgeware" "mLib utilities library"
+.SH NAME
+dstr \- a simple dynamic string type
+.\" @dstr_create
+.\" @dstr_destroy
+.\" @dstr_reset
+.\" @dstr_ensure
+.\" @dstr_tidy
+.\"
+.\" @dstr_putc
+.\" @dstr_putz
+.\" @dstr_puts
+.\" @dstr_putf
+.\" @dstr_putd
+.\" @dstr_putm
+.\" @dstr_putline
+.\" @dstr_write
+.\"
+.\" @DSTR_INIT
+.\" @DCREATE
+.\" @DDESTROY
+.\" @DRESET
+.\" @DENSURE
+.\" @DPUTC
+.\" @DPUTZ
+.\" @DPUTS
+.\" @DPUTD
+.\" @DPUTM
+.\" @DWRITE
+.\"
+.SH SYNOPSIS
+.nf
+.B "#include <mLib/dstr.h>"
+
+.BI "void dstr_create(dstr *" d );
+.BI "void dstr_destroy(dstr *" d );
+.BI "void dstr_reset(dstr *" d );
+
+.BI "void dstr_ensure(dstr *" d ", size_t " sz );
+.BI "void dstr_tidy(dstr *" d );
+
+.BI "void dstr_putc(dstr *" d ", int " ch );
+.BI "void dstr_putz(dstr *" d );
+.BI "void dstr_puts(dstr *" d ", const char *" s );
+.BI "int dstr_vputf(dstr *" d ", va_list *" ap );
+.BI "int dstr_putf(dstr *" d ", ...);"
+.BI "void dstr_putd(dstr *" d ", const dstr *" p );
+.BI "void dstr_putm(dstr *" d ", const void *" p ", size_t " sz );
+.BI "int dstr_putline(dstr *" d ", FILE *" fp );
+.BI "size_t dstr_write(const dstr *" d ", FILE *" fp );
+
+.BI "dstr " d " = DSTR_INIT;"
+.BI "void DCREATE(dstr *" d );
+.BI "void DDESTROY(dstr *" d );
+.BI "void DRESET(dstr *" d );
+.BI "void DENSURE(dstr *" d ", size_t " sz );
+.BI "void DPUTC(dstr *" c ", char " ch );
+.BI "void DPUTZ(dstr *" d );
+.BI "void DPUTS(dstr *" d ", const char *" s );
+.BI "void DPUTD(dstr *" d ", const dstr *" p );
+.BI "void DPUTM(dstr *" d ", const void *" p ", size_t " sz );
+.BI "size_t DWRITE(const dstr *" d ", FILE *" fp );
+.fi
+.SH DESCRIPTION
+The header
+.B dstr.h
+declares a type for representing dynamically extending strings, and a
+small collection of useful operations on them.  None of the operations
+returns a failure result on an out-of-memory condition; instead, the
+exception
+.B EXC_NOMEM
+is raised.
+.PP
+Many of the functions which act on dynamic strings have macro
+equivalents.  These equivalent macros may evaluate their arguments
+multiple times.
+.SS "Underlying type"
+A
+.B dstr
+object is a small structure with the following members:
+.VS
+typedef struct dstr {
+  char *buf;           /* Pointer to string buffer */
+  size_t sz;           /* Size of the buffer */
+  size_t len;          /* Length of the string */
+  arena *a;            /* Pointer to arena */
+} dstr;
+.VE
+The
+.B buf
+member points to the actual character data in the string.  The data may
+or may not be null terminated, depending on what operations have
+recently been performed on it.  None of the
+.B dstr
+functions depend on the string being null-terminated; indeed, all of
+them work fine on strings containing arbitrary binary data.  You can
+force null-termination by calling the
+.B dstr_putz
+function, or the
+.B DPUTZ
+macro.
+.PP
+The
+.B sz
+member describes the current size of the buffer.  This reflects the
+maximum possible length of string that can be represented in
+.B buf
+without allocating a new buffer.
+.PP
+The
+.B len
+member describes the current length of the string.  It is the number of
+bytes in the string which are actually interesting.  The length does
+.I not
+include a null-terminating byte, if there is one.
+.PP
+The following invariants are maintained by
+.B dstr
+and must hold when any function is called:
+.hP \*o
+If
+.B sz
+is nonzero, then
+.B buf
+points to a block of memory of length
+.BR sz .
+If
+.B sz
+is zero, then
+.B buf
+is a null pointer.
+.hP \*o
+At all times,
+.BR sz " \(>= " len.
+.PP
+Note that there is no equivalent of the standard C distinction between
+the empty string (a pointer to an array of characters whose first
+element is zero) and the nonexistent string (a null pointer).  Any
+.B dstr
+whose
+.B len
+is zero is an empty string.
+.PP
+The
+.I a
+member refers to the arena from which the string's buffer has been
+allocated.  Immediately after creation, this is set to be
+.BR arena_stdlib (3);
+you can set it to point to any other arena of your choice before the
+buffer is allocated.
+.SS "Creation and destruction"
+The caller is responsible for allocating the
+.B dstr
+structure.  It can be initialized:
+.hP \*o
+using the macro
+.B DSTR_INIT
+as an initializer in the declaration of the object,
+.hP \*o
+passing its address to the
+.B dstr_create
+function, or
+.hP \*o
+passing its address to the (equivalent)
+.B DCREATE
+macro.
+.PP
+The initial value of a
+.B dstr
+is the empty string.
+.PP
+The additional storage space for a string's contents may be reclaimed by
+passing it to the
+.B dstr_destroy
+function, or the
+.B DDESTROY
+macro.  After destruction, a string's value is reset to the empty
+string:
+.I "it's still a valid"
+.BR dstr .
+However, once a string has been destroyed, it's safe to deallocate the
+underlying
+.B dstr
+object.
+.PP
+The
+.B dstr_reset
+function empties a string
+.I without
+deallocating any memory.  Therefore appending more characters is quick,
+because the old buffer is still there and doesn't need to be allocated.
+Calling
+.VS
+dstr_reset(d);
+.VE
+is equivalent to directly assigning
+.VS
+d->len = 0;
+.VE
+There's also a macro
+.B DRESET
+which does the same job as the
+.B dstr_reset
+function.
+.SS "Extending a string"
+All memory allocation for strings is done by the function
+.BR dstr_ensure .
+Given a pointer
+.I d
+to a
+.B dstr
+and a size
+.IR sz ,
+the function ensures that there are at least
+.I sz
+unused bytes in the string's buffer.  The current algorithm for
+extending the buffer is fairly unsophisticated, but seems to work
+relatively well \- see the source if you really want to know what it's
+doing.
+.PP
+Extending a string never returns a failure result.  Instead, if there
+isn't enough memory for a longer string, the exception
+.B EXC_NOMEM
+is raised.  See
+.BR exc (3)
+for more information about
+.BR mLib 's
+exception handling system.
+.PP
+Note that if an ensure operation needs to reallocate a string buffer,
+any pointers you've taken into the string become invalid.
+.PP
+There's a macro
+.B DENSURE
+which does a quick inline check to see whether there's enough space in
+a string's buffer.  This saves a procedure call when no reallocation
+needs to be done.  The
+.B DENSURE
+macro is called in the same way as the
+.B dstr_ensure
+function.
+.PP
+The function
+.B dstr_tidy
+`trims' a string's buffer so that it's just large enough for the string
+contents and a null terminating byte.  This might raise an exception due
+to lack of memory.  (There are two possible ways this might happen.
+Firstly, the underlying allocator might just be brain-damaged enough to
+fail on reducing a block's size.  Secondly, tidying an empty string with no
+buffer allocated for it causes allocation of a buffer large enough for
+the terminating null byte.)
+.SS "Contributing data to a string"
+There are a collection of functions which add data to a string.  All of
+these functions add their new data to the
+.I end
+of the string.  This is good, because programs usually build strings
+left-to-right.  If you want to do something more clever, that's up to
+you.
+.PP
+Several of these functions have equivalent macros which do the main work
+inline.  (There still might need to be a function call if the buffer
+needs to be extended.)
+.PP
+Any of these functions might extend the string, causing pointers into
+the string buffer to be invalidated.  If you don't want that to happen,
+pre-ensure enough space before you start.
+.PP
+The simplest function is
+.B dstr_putc
+which appends a single character
+.I ch
+to the end of the string.  It has a macro equivalent called
+.BR DPUTC .
+.PP
+The function
+.B dstr_putz
+places a zero byte at the end of the string.  It does
+.I not
+affect the string's length, so any other data added to the string will
+overwrite the null terminator.  This is useful if you want to pass your
+string to one of the standard C library string-handling functions.  The
+macro
+.B DPUTZ
+does the same thing.
+.PP
+The function
+.B dstr_puts
+writes a C-style null-terminated string to the end of a dynamic string.
+A terminating zero byte is also written, as if
+.B dstr_putz
+were called.  The macro
+.B DPUTS
+does the same job.
+.PP
+The function
+.B dstr_putf
+works similarly to the standard
+.BR sprintf (3)
+function.  It accepts a
+.BR print (3)-style
+format string and an arbitrary number of arguments to format and writes
+the resulting text to the end of a dynamic string, returning the number
+of characters so written.  A terminating zero byte is also appended.
+The formatting is intended to be convenient and safe rather than
+efficient, so don't expect blistering performance.  Similarly, there may
+be differences between the formatting done by
+.B dstr_putf
+and
+.BR sprintf (3)
+because the former has to do most of its work itself.  In particular,
+.B dstr_putf
+understands the POSIX
+.RB ` n$ '
+positional parameter notation accepted by many Unix C libraries, even if
+the underlying C library does not.  There is no macro equivalent of
+.BR dstr_putf .
+.PP
+The function
+.B dstr_vputf
+provides access to the `guts' of
+.BR dstr_putf :
+given a format string and a pointer to a
+.BR va_list
+it will format the arguments according to the format string, just as
+.B dstr_putf
+does.  (Note: that's a
+.BR "va_list *" ,
+not a plain
+.BR va_list ,
+so that it gets updated properly on exit.)
+.PP
+The function
+.B dstr_putd
+appends the contents of one dynamic string to another.  A null
+terminator is also appended.  The macro
+.B DPUTD
+does the same thing.
+.PP
+The function
+.B dstr_putm
+puts an arbitrary block of memory, addressed by
+.IR p ,
+with length
+.I sz
+bytes, at the end of a dynamic string.  No terminating null is appended:
+it's assumed that if you're playing with arbitrary chunks of memory then
+you're probably not going to be using the resulting data as a normal
+text string.  The macro
+.B DPUTM
+works the same way.
+.PP
+The function
+.B dstr_putline
+reads a line from an input stream
+.I fp
+and appends it to a string.  If an error occurs, or end-of-file is
+encountered, before any characters have been read, then
+.B dstr_putline
+returns the value
+.B EOF
+and does not extend the string.  Otherwise, it reads until it encounters
+a newline character, an error, or end-of-file, and returns the number of
+characters read.  If reading was terminated by a newline character, the
+newline character is
+.I not
+inserted in the buffer.  A terminating null is appended, as by
+.BR dstr_putz .
+.SS "Other functions"
+The
+.B dstr_write
+function writes a string to an output stream
+.IR fp .
+It returns the number of characters written, or
+.B 0
+if an error occurred before the first write.  No newline character is
+written to the stream, unless the string actually contains one already.
+The macro
+.B DWRITE
+is equivalent.
+.SH "SECURITY CONSIDERATIONS"
+The implementation of the
+.B dstr
+functions is designed to do string handling in security-critical
+programs.  However, there may be bugs in the code somewhere.  In
+particular, the
+.B dstr_putf
+functions are quite complicated, and could do with some checking by
+independent people who know what they're doing.
+.SH "SEE ALSO"
+.BR exc (3),
+.BR mLib (3).
+.SH AUTHOR
+Mark Wooding, <mdw@distorted.org.uk>
diff --git a/struct/dstr.c b/struct/dstr.c
new file mode 100644 (file)
index 0000000..7e154ae
--- /dev/null
@@ -0,0 +1,264 @@
+/* -*-c-*-
+ *
+ * Handle dynamically growing strings
+ *
+ * (c) 1998 Straylight/Edgeware
+ */
+
+/*----- Licensing notice --------------------------------------------------*
+ *
+ * This file is part of the mLib utilities library.
+ *
+ * mLib is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * mLib is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with mLib; if not, write to the Free
+ * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+ * MA 02111-1307, USA.
+ */
+
+/*----- Header files ------------------------------------------------------*/
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "alloc.h"
+#include "dstr.h"
+
+/*----- Tunable constants -------------------------------------------------*/
+
+/*
+ * If the buffer is empty, it is set to @DSTR_INITSZ@ bytes in size.
+ * Otherwise, it's set to the next power of two that's large enough.  This is
+ * memory-hungry, but efficient.
+ */
+
+#define DSTR_INITSZ 64                 /* Initial buffer size */
+
+/*----- Main code ---------------------------------------------------------*/
+
+/* --- @dstr_create@ --- *
+ *
+ * Arguments:  @dstr *d@ = pointer to a dynamic string block
+ *
+ * Returns:    ---
+ *
+ * Use:                Initializes a dynamic string.
+ */
+
+void dstr_create(dstr *d) { DCREATE(d); }
+
+/* --- @dstr_destroy@ --- *
+ *
+ * Arguments:  @dstr *d@ = pointer to a dynamic string block
+ *
+ * Returns:    ---
+ *
+ * Use:                Reclaims the space used by a dynamic string.
+ */
+
+void dstr_destroy(dstr *d) { DDESTROY(d); }
+
+/* --- @dstr_reset@ --- *
+ *
+ * Arguments:  @dstr *d@ = pointer to a dynamic string block
+ *
+ * Returns:    ---
+ *
+ * Use:                Resets a string so that new data gets put at the beginning.
+ */
+
+void dstr_reset(dstr *d) { DRESET(d); }
+
+/* --- @dstr_ensure@ --- *
+ *
+ * Arguments:  @dstr *d@ = pointer to a dynamic string block
+ *             @size_t sz@ = amount of free space to ensure
+ *
+ * Returns:    ---
+ *
+ * Use:                Ensures that at least @sz@ bytes are available in the
+ *             given string.
+ */
+
+void dstr_ensure(dstr *d, size_t sz)
+{
+  size_t rq = d->len + sz;
+  size_t nsz;
+
+  /* --- If we have enough space, just leave it --- */
+
+  if (rq <= d->sz)
+    return;
+
+  /* --- Grow the buffer --- */
+
+  nsz = d->sz;
+
+  if (nsz == 0)
+    nsz = (DSTR_INITSZ >> 1);
+  do nsz <<= 1; while (nsz < rq);
+
+  if (d->buf)
+    d->buf = x_realloc(d->a, d->buf, nsz, d->sz);
+  else
+    d->buf = x_alloc(d->a, nsz);
+  d->sz = nsz;
+}
+
+/* --- @dstr_putc@ --- *
+ *
+ * Arguments:  @dstr *d@ = pointer to a dynamic string block
+ *             @int ch@ = character to append
+ *
+ * Returns:    ---
+ *
+ * Use:                Appends a character to a string.
+ */
+
+void dstr_putc(dstr *d, int ch) { DPUTC(d, ch); }
+
+/* --- @dstr_putz@ --- *
+ *
+ * Arguments:  @dstr *d@ = pointer to a dynamic string block
+ *
+ * Returns:    ---
+ *
+ * Use:                Appends a null byte to a string.  The null byte does not
+ *             contribute to the string's length, and will be overwritten
+ *             by subsequent `put' operations.
+ */
+
+void dstr_putz(dstr *d) { DPUTZ(d); }
+
+/* --- @dstr_puts@ --- *
+ *
+ * Arguments:  @dstr *d@ = pointer to a dynamic string block
+ *             @const char *s@ = pointer to string to append
+ *
+ * Returns:    ---
+ *
+ * Use:                Appends a character string to a string.  A trailing null
+ *             byte is added, as for @dstr_putz@.
+ */
+
+void dstr_puts(dstr *d, const char *s) { DPUTS(d, s); }
+
+/* --- @dstr_putd@ --- *
+ *
+ * Arguments:  @dstr *d@ = pointer to a dynamic string block
+ *             @const dstr *s@ = pointer to a dynamic string to append
+ *
+ * Returns:    ---
+ *
+ * Use:                Appends a dynamic string to a string.  A trailing null
+ *             byte is added, as for @dstr_putz@.
+ */
+
+void dstr_putd(dstr *d, const dstr *s) { DPUTD(d, s); }
+
+/* --- @dstr_putm@ --- *
+ *
+ * Arguments:  @dstr *d@ = pointer to a dynamic string block
+ *             @const void *p@ = pointer to a block to append
+ *             @size_t sz@ = size of the block
+ *
+ * Returns:    Appends an arbitrary data block to a string.  No trailing
+ *             null is appended.
+ */
+
+void dstr_putm(dstr *d, const void *p, size_t sz) { DPUTM(d, p, sz); }
+
+/* --- @dstr_tidy@ --- *
+ *
+ * Arguments:  @dstr *d@ = pointer to a dynamic string block
+ *
+ * Returns:    ---
+ *
+ * Use:                Reduces the amount of memory used by a string.  A trailing
+ *             null byte is added, as for @dstr_putz@.
+ */
+
+void dstr_tidy(dstr *d)
+{
+  d->buf = x_realloc(d->a, d->buf, d->len + 1, d->sz);
+  d->buf[d->len] = 0;
+  d->sz = d->len + 1;
+}
+
+/* --- @dstr_putline@ --- *
+ *
+ * Arguments:  @dstr *d@ = pointer to a dynamic string block
+ *             @FILE *fp@ = a stream to read from
+ *
+ * Returns:    The number of characters read into the buffer, or @EOF@ if
+ *             end-of-file was reached before any characters were read.
+ *
+ * Use:                Appends the next line from the given input stream to the
+ *             string.  A trailing newline is not added; a trailing null
+ *             byte is appended, as for @dstr_putz@.
+ */
+
+int dstr_putline(dstr *d, FILE *fp)
+{
+  size_t left = d->sz - d->len;
+  size_t off = d->len;
+  int rd = 0;
+  int ch;
+
+  for (;;) {
+
+    /* --- Read the next byte --- */
+
+    ch = getc(fp);
+
+    /* --- End-of-file when no characters read is special --- */
+
+    if (ch == EOF && !rd)
+      return (EOF);
+
+    /* --- Make sure there's some buffer space --- */
+
+    if (!left) {
+      d->len = off;
+      dstr_ensure(d, 1);
+      left = d->sz - off;
+    }
+
+    /* --- End-of-file or newline ends the loop --- */
+
+    if (ch == EOF || ch == '\n') {
+      d->buf[off] = 0;
+      d->len = off;
+      return rd;
+    }
+
+    /* --- Append the character and continue --- */
+
+    d->buf[off++] = ch;
+    left--; rd++;
+  }
+}
+
+/* --- @dstr_write@ --- *
+ *
+ * Arguments:  @dstr *d@ = pointer to a dynamic string block
+ *             @FILE *fp@ = a stream to write on
+ *
+ * Returns:    The number of bytes written (as for @fwrite@).
+ *
+ * Use:                Writes a dynamic string to a file.
+ */
+
+size_t dstr_write(const dstr *d, FILE *fp) { return (DWRITE(d, fp)); }
+
+/*----- That's all, folks -------------------------------------------------*/
diff --git a/struct/dstr.h b/struct/dstr.h
new file mode 100644 (file)
index 0000000..1c43cb7
--- /dev/null
@@ -0,0 +1,324 @@
+/* -*-c-*-
+ *
+ * Handle dynamically growing strings
+ *
+ * (c) 1998 Straylight/Edgeware
+ */
+
+/*----- Licensing notice --------------------------------------------------*
+ *
+ * This file is part of the mLib utilities library.
+ *
+ * mLib is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * mLib is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with mLib; if not, write to the Free
+ * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+ * MA 02111-1307, USA.
+ */
+
+#ifndef MLIB_DSTR_H
+#define MLIB_DSTR_H
+
+#ifdef __cplusplus
+  extern "C" {
+#endif
+
+/*----- Rationale ---------------------------------------------------------*
+ *
+ * This file declares what is hopefully a fairly useful collection of
+ * primitive string handling functions.  The idea is that the strings
+ * allocate memory for themselves as required.  The @dstr@ routines don't
+ * assume any sort of terminator character, so arbitrary binary data can
+ * be stored in a dynamic string.  With luck, this should put a stop to
+ * any buffer overflow problems.
+ */
+
+/*----- Header files ------------------------------------------------------*/
+
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#ifndef MLIB_ALLOC_H
+#  include "alloc.h"
+#endif
+
+#ifndef MLIB_ARENA_H
+#  include "arena.h"
+#endif
+
+#ifndef MLIB_MACROS_H
+#  include "macros.h"
+#endif
+
+/*----- Data structures ---------------------------------------------------*/
+
+typedef struct dstr {
+  char *buf;                           /* Pointer to string buffer */
+  size_t sz;                           /* Size of the buffer */
+  size_t len;                          /* Length of the string */
+  arena *a;                            /* Pointer to arena */
+} dstr;
+
+#define DSTR_INIT { 0, 0, 0, &arena_stdlib } /* How to initialize one */
+
+/*----- Functions provided ------------------------------------------------*/
+
+/* --- @dstr_create@ --- *
+ *
+ * Arguments:  @dstr *d@ = pointer to a dynamic string block
+ *
+ * Returns:    ---
+ *
+ * Use:                Initializes a dynamic string.
+ */
+
+extern void dstr_create(dstr */*d*/);
+
+#define DCREATE(d) do {                                                        \
+  dstr *_dd = (d);                                                     \
+  _dd->buf = 0;                                                                \
+  _dd->sz = 0;                                                         \
+  _dd->len = 0;                                                                \
+  _dd->a = &arena_stdlib;                                              \
+} while (0)
+
+/* --- @dstr_destroy@ --- *
+ *
+ * Arguments:  @dstr *d@ = pointer to a dynamic string block
+ *
+ * Returns:    ---
+ *
+ * Use:                Reclaims the space used by a dynamic string.
+ */
+
+extern void dstr_destroy(dstr */*d*/);
+
+#define DDESTROY(d) do {                                               \
+  dstr *_d = (d);                                                      \
+  if (_d->buf)                                                         \
+    x_free(_d->a, _d->buf);                                            \
+  DCREATE(_d);                                                         \
+} while (0)
+
+/* --- @dstr_reset@ --- *
+ *
+ * Arguments:  @dstr *d@ = pointer to a dynamic string block
+ *
+ * Returns:    ---
+ *
+ * Use:                Resets a string so that new data gets put at the beginning.
+ */
+
+extern void dstr_reset(dstr */*d*/);
+
+#define DRESET(d) ((d)->len = 0)
+
+/* --- @dstr_ensure@ --- *
+ *
+ * Arguments:  @dstr *d@ = pointer to a dynamic string block
+ *             @size_t sz@ = amount of free space to ensure
+ *
+ * Returns:    ---
+ *
+ * Use:                Ensures that at least @sz@ bytes are available in the
+ *             given string.
+ */
+
+extern void dstr_ensure(dstr */*d*/, size_t /*sz*/);
+
+#define DENSURE(d, rq) do {                                            \
+  dstr *_dd = (d);                                                     \
+  size_t _rq = (rq);                                                   \
+  if (_dd->len + _rq > _dd->sz) dstr_ensure(_dd, _rq);                 \
+} while (0)
+
+/* --- @dstr_putc@ --- *
+ *
+ * Arguments:  @dstr *d@ = pointer to a dynamic string block
+ *             @int ch@ = character to append
+ *
+ * Returns:    ---
+ *
+ * Use:                Appends a character to a string.
+ */
+
+extern void dstr_putc(dstr */*d*/, int /*ch*/);
+
+#define DPUTC(d, ch) do {                                              \
+  dstr *_d = (d);                                                      \
+  DENSURE(_d, 1);                                                      \
+  *((unsigned char *)_d->buf + _d->len++) = (ch);                      \
+} while (0)
+
+/* --- @dstr_putz@ --- *
+ *
+ * Arguments:  @dstr *d@ = pointer to a dynamic string block
+ *
+ * Returns:    ---
+ *
+ * Use:                Appends a null byte to a string.  The null byte does not
+ *             contribute to the string's length, and will be overwritten
+ *             by subsequent `put' operations.
+ */
+
+extern void dstr_putz(dstr */*d*/);
+
+#define DPUTZ(d) do {                                                  \
+  dstr *_d = (d);                                                      \
+  DENSURE(_d, 1);                                                      \
+  _d->buf[_d->len] = 0;                                                \
+} while (0)
+
+/* --- @dstr_puts@ --- *
+ *
+ * Arguments:  @dstr *d@ = pointer to a dynamic string block
+ *             @const char *s@ = pointer to string to append
+ *
+ * Returns:    ---
+ *
+ * Use:                Appends a character string to a string.  A trailing null
+ *             byte is added, as for @dstr_putz@.
+ */
+
+extern void dstr_puts(dstr */*d*/, const char */*s*/);
+
+#define DPUTS(d, s) do {                                               \
+  dstr *_d = (d);                                                      \
+  const char *_s = (s);                                                        \
+  size_t _sz = strlen(_s);                                             \
+  DENSURE(_d, _sz + 1);                                                        \
+  memcpy(_d->buf + _d->len, _s, _sz + 1);                              \
+  _d->len += _sz;                                                      \
+} while (0)
+
+/* --- @dstr_vputf@ --- *
+ *
+ * Arguments:  @dstr *d@ = pointer to a dynamic string block
+ *             @const char *p@ = pointer to @printf@-style format string
+ *             @va_list *ap@ = argument handle
+ *
+ * Returns:    The number of characters written to the string.
+ *
+ * Use:                As for @dstr_putf@, but may be used as a back-end to user-
+ *             supplied functions with @printf@-style interfaces.
+ */
+
+extern int dstr_vputf(dstr */*d*/, const char */*p*/, va_list */*ap*/);
+
+/* --- @dstr_putf@ --- *
+ *
+ * Arguments:  @dstr *d@ = pointer to a dynamic string block
+ *             @const char *p@ = pointer to @printf@-style format string
+ *             @...@ = argument handle
+ *
+ * Returns:    The number of characters written to the string.
+ *
+ * Use:                Writes a piece of text to a dynamic string, doing @printf@-
+ *             style substitutions as it goes.  Intended to be robust if
+ *             faced with malicious arguments, but not if the format string
+ *             itself is malicious.
+ */
+
+extern int PRINTF_LIKE(2, 3)
+  dstr_putf(dstr */*d*/, const char */*p*/, ...);
+
+/* --- @dstr_putd@ --- *
+ *
+ * Arguments:  @dstr *d@ = pointer to a dynamic string block
+ *             @const dstr *s@ = pointer to a dynamic string to append
+ *
+ * Returns:    ---
+ *
+ * Use:                Appends a dynamic string to a string.  A trailing null
+ *             byte is added, as for @dstr_putz@.
+ */
+
+extern void dstr_putd(dstr */*d*/, const dstr */*s*/);
+
+#define DPUTD(d, s) do {                                               \
+  dstr *_d = (d);                                                      \
+  const dstr *_s = (s);                                                        \
+  DENSURE(_d, _s->len + 1);                                            \
+  memcpy(_d->buf + _d->len, _s->buf, _s->len);                         \
+  _d->len += _s->len;                                                  \
+  _d->buf[_d->len] = 0;                                                        \
+} while (0)
+
+/* --- @dstr_putm@ --- *
+ *
+ * Arguments:  @dstr *d@ = pointer to a dynamic string block
+ *             @const void *p@ = pointer to a block to append
+ *             @size_t sz@ = size of the block
+ *
+ * Returns:    Appends an arbitrary data block to a string.  No trailing
+ *             null is appended.
+ */
+
+extern void dstr_putm(dstr */*d*/, const void */*p*/, size_t /*sz*/);
+
+#define DPUTM(d, p, sz) do {                                           \
+  dstr *_d = (d);                                                      \
+  size_t _sz = (sz);                                                   \
+  DENSURE(_d, _sz);                                                    \
+  memcpy(_d->buf + _d->len, (p), _sz);                                 \
+  _d->len += _sz;                                                      \
+} while (0)
+
+/* --- @dstr_tidy@ --- *
+ *
+ * Arguments:  @dstr *d@ = pointer to a dynamic string block
+ *
+ * Returns:    ---
+ *
+ * Use:                Reduces the amount of memory used by a string.  A trailing
+ *             null byte is added, as for @dstr_putz@.
+ */
+
+extern void dstr_tidy(dstr */*d*/);
+
+/* --- @dstr_putline@ --- *
+ *
+ * Arguments:  @dstr *d@ = pointer to a dynamic string block
+ *             @FILE *fp@ = a stream to read from
+ *
+ * Returns:    The number of characters read into the buffer, or @EOF@ if
+ *             end-of-file was reached before any characters were read.
+ *
+ * Use:                Appends the next line from the given input stream to the
+ *             string.  A trailing newline is not added; a trailing null
+ *             byte is appended, as for @dstr_putz@.
+ */
+
+extern int dstr_putline(dstr */*d*/, FILE */*fp*/);
+
+/* --- @dstr_write@ --- *
+ *
+ * Arguments:  @dstr *d@ = pointer to a dynamic string block
+ *             @FILE *fp@ = a stream to write on
+ *
+ * Returns:    The number of bytes written (as for @fwrite@).
+ *
+ * Use:                Writes a dynamic string to a file.
+ */
+
+extern size_t dstr_write(const dstr */*d*/, FILE */*fp*/);
+
+#define DWRITE(d, fp) fwrite((d)->buf, 1, (d)->len, (fp))
+
+/*----- That's all, folks -------------------------------------------------*/
+
+#ifdef __cplusplus
+  }
+#endif
+
+#endif
diff --git a/struct/hash.3 b/struct/hash.3
new file mode 100644 (file)
index 0000000..5869d89
--- /dev/null
@@ -0,0 +1,294 @@
+.\" -*-nroff-*-
+.de VS
+.sp 1
+.RS
+.nf
+.ft B
+..
+.de VE
+.ft R
+.fi
+.RE
+.sp 1
+..
+.de hP
+.IP
+.ft B
+\h'-\w'\\$1\ 'u'\\$1\ \c
+.ft P
+..
+.ie t .ds o \(bu
+.el .ds o o
+.TH hash 3 "2 August 1999" "Straylight/Edgeware" "mLib utilities library"
+.SH "NAME"
+hash \- low-level hashtable implementation
+.\" @hash_create
+.\" @hash_destroy
+.\" @hash_bin
+.\" @hash_extend
+.\" @hash_remove
+.\" @hash_mkiter
+.\" @hash_next
+.\"
+.\" @HASH_BIN
+.\" @HASH_MKITER
+.\" @HASH_NEXT
+.\"
+.SH "SYNOPSIS"
+.nf
+.B "#include <mLib/hash.h>"
+
+.BI "void hash_create(hash_table *" t ", size_t " n );
+.BI "void hash_destroy(hash_table *" t );
+.BI "hash_base **hash_bin(hash_table *" t ", uint32 " hash );
+.BI "int hash_extend(hash_table *" t );
+.BI "void hash_remove(hash_table *" t ", hash_base * " b );
+.BI "void hash_mkiter(hash_iter *" i ", hash_table *" t );
+.BI "hash_base *hash_next(hash_iter *" i );
+
+.BI "hash_base **HASH_BIN(hash_table *" t ", uint32 " hash );
+.BI "void HASH_MKITER(hash_iter *" i ", hash_table *" t );
+.BI "void HASH_NEXT(hash_iter *" i ", " b );
+.fi
+.SH "OVERVIEW"
+The
+.B hash
+functions provide the basis for an extensible hashtable implementation.
+The implementation is not complete.  Many decisions have been left to
+the user, including:
+.hP \*o
+how keys should be represented, hashed and compared;
+.hP \*o
+how objects contained within the table should be allocated; and
+.hP \*o
+when the hashtable should be extended.
+.PP
+A complete hashtable implementation will need to take the above
+decisions.  If you just want a prepackaged solution, see
+.BR sym (3)
+which provides one.
+.SH "IMPLEMENTATION DETAILS"
+Each item in the hashtable is assigned a 32-bit integer
+.IR hash :
+a number computed somehow from the item's data such that two items which
+are considered equal will yield the same hash.  Ideally, different items
+will yield different hashes.  It is important for this implementation
+that all bits of the hash are similarly good.
+.PP
+In order to look up an item, the high bits of the hash are masked off
+and the remainder used as an index into a vector of
+.IR "bin lists" .
+Each bin contains a list of items with identical low-order bits of their
+hashes.
+.PP
+A table expansion involves doubling the size of the bin vector.  Each
+bin list is then split into two, items being placed into a new bin
+depending on the setting of the next lowest hash bit.  By expanding the
+hashtable as needed, lookups remain constant-time.
+.PP
+A low-level hashtable is represented by a
+.B hash_table
+structure.  It contains two members:
+.TP
+.B "uint32 mask"
+The current bitmask to be applied to hashes.  This is one less than the
+current number of bins in the hashtable, and is applied to hash values
+in order to decide which bin an item should be in.
+.TP
+.B "hash_base **v"
+The bin vector.  It is an array of pointers to hashtable items.
+.PP
+A hashtable item consists of a
+.B hash_base
+structure.  A full hashtable implementation will need to extend this
+structure by adding keying information and other data; the
+.B hash_base
+only contains the bare minimum of information needed to maintain the
+hashtable at a low level.  It contains the following members:
+.TP
+.B "hash_base *next"
+Pointer to the next item in the bin list.  The final item has a null
+.B next
+pointer.  The entry in the bin vector is null if the bin list is empty.
+It is up to the high-level implementation to insert items into the list.
+.TP
+.B "uint32 hash"
+The hash for this item.  This must be the full 32-bit hash for the
+current item.  It is used during hashtable expansion to determine which
+bin an item should be moved to.
+.SH "FUNCTIONALITY PROVIDED"
+This section describes the functions and macros provided for building
+hashtables.  Code examples are given throughout.  They assume the
+following definitions:
+.VS
+/* --- A table of items --- */
+
+typedef struct item_table {
+  hash_table t;
+  size_t load;
+};
+
+/* --- An item --- */
+
+typedef struct item {
+  hash_base b;
+  const char *k;
+  /* ... */
+} item;
+.VE
+The implementation presented here is simple but relatively bad.  The
+source file
+.B sym.c
+presents a more realistic example, but is rather more complex.
+.SS "Initialization and finalization"
+An empty hashtable is initialized by calling
+.B hash_create
+with the address of a
+.B hash_table
+structure to be filled in and the initial number of hash bins to create.
+.PP
+For example, an item table might be initialized like this:
+.VS
+void item_createtab(item_table *t)
+{
+  hash_create(&t->t, ITEM_INITSZ);
+  t->load = ITEM_INITLOAD;
+}
+.VE
+A hashtable can be destroyed by calling
+.B hash_destroy
+with the address of the
+.B hash_table
+structure.  This does not attempt to deallocate the individual items;
+that must be done beforehand.
+.PP
+The usual way to deallocate the individual hashtable items is using the
+iteration constructs described below.
+.VS
+void item_destroytab(item_table *t)
+{
+  hash_iter i;
+  hash_base *b;
+  for (hash_mkiter(&i, &t->t); (b = hash_next(&i)) != 0; ) {
+    item *ii = (item *)b;
+    free(ii->k);
+    /* ... */
+    DESTROY(ii);
+  }
+  hash_destroy(&t->t);
+}
+.VE
+.sp -1
+.SS "Searching, adding and removing"
+Items must be searched for and added by hand.
+.PP
+The macro
+.B HASH_BIN
+returns the address of the bin list haed for a particular hashtable and
+hash value.  The function
+.B hash_bin
+works the same way and provides the same result, but since the macro is
+very simple its use is preferred.  However, it will evaluate its
+arguments multiple times.
+.PP
+Once the bin list has been found, it's fairly easy to search for an
+exact match.  A simple search might look something like this:
+.VS
+item *lookup(item_table *t, const char *k)
+{
+  uint32 h = hash(k);          /* Hash @k@ somehow */
+  hash_base **bin = HASH_BIN(&t->t, h);
+  hash_base *b;
+  for (b = *bin; b; b = b->next) {
+    item *i = (item *)b;
+    if (h == i->b.hash && strcmp(k, i->k) == 0)
+      return (i);
+  }
+  return (0);
+}
+.VE
+Insertion is also relatively trivial given the bin list head.  Insertion
+may make the hashtable too large, so it might be necessary to extend
+it.  Extension is performed by
+.BR hash_extend ,
+which is passed only the address of the hashtable.  It returns nonzero
+if extension was successful.
+.VS
+item *add(item_table *t, const char *k, /* ... */)
+{
+  item *i;
+  uint32 h;
+  hash_base **bin;
+
+  /* --- See if the item is already there --- */
+
+  if ((i =  = lookup(t, k)) != 0)
+    return (i);
+
+  /* --- Make a new hashtable item --- */
+
+  i = CREATE(item);
+  i->k = xstrdup(k);
+  /* ... */
+
+  /* --- Link it into the bin list --- */
+
+  h = i->b.hash = hash(k);
+  bin = HASH_BIN(&t->t, h);
+  i->b.next = *bin;
+  *bin = &i->b.next;
+
+  /* --- Maybe extend the hashtable --- */
+
+  if (t->load)
+    t->load--;
+  else if (hash_extend(&t->t))
+    t->load = recalc_load(t);
+
+  /* --- Done --- */
+
+  return (i);
+}
+.VE
+The
+.B sym
+implementation is rather more sophisticated in its approach but the idea
+is the same.  In particular,
+.B sym
+provides a single interface for lookup and insertion which prevents the
+unnecessary rehashing performed by the above code.
+.PP
+Removal of items is more straightforward.  The function
+.B hash_remove
+will unlink a given item from its bin list, after which point it is safe
+to remove.
+.SS "Iteration"
+Iteration allows code to be performed on all the items in a hashtable.
+This is done using an
+.I iterator
+object, represented by a
+.B hash_iter
+structure.  An iterator is initialized by calling
+.BR hash_mkiter .
+Each call to
+.B hash_next
+yields a different item from the hashtable until there are none left, a
+condition signified by a null return value.
+.PP
+The macros
+.B HASH_MKITER
+and
+.B HASH_NEXT
+do the same jobs as the above functions.  However,
+.B HASH_NEXT
+has a slightly bizarre argument passing convention: its second argument
+is an
+.I lvalue
+which is updated to contain the address of the next item.
+.PP
+The finalization code above contained an example of iteration.
+.SH "SEE ALSO"
+.BR sym (3),
+.BR mLib (3).
+.SH "AUTHOR"
+Mark Wooding, <mdw@distorted.org.uk>
diff --git a/struct/hash.c b/struct/hash.c
new file mode 100644 (file)
index 0000000..d4df9b7
--- /dev/null
@@ -0,0 +1,192 @@
+/* -*-c-*-
+ *
+ * General hashtable infrastructure
+ *
+ * (c) 1999 Straylight/Edgeware
+ */
+
+/*----- Licensing notice --------------------------------------------------*
+ *
+ * This file is part of the mLib utilities library.
+ *
+ * mLib is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * mLib is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with mLib; if not, write to the Free
+ * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+ * MA 02111-1307, USA.
+ */
+
+/*----- Header files ------------------------------------------------------*/
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "alloc.h"
+#include "arena.h"
+#include "bits.h"
+#include "exc.h"
+#include "hash.h"
+
+/*----- Main code ---------------------------------------------------------*/
+
+/* --- @hash_create@ --- *
+ *
+ * Arguments:  @hash_table *t@ = pointer to hashtable to initialize
+ *             @size_t n@ = number of bins to allocate (zero initially)
+ *
+ * Returns:    ---
+ *
+ * Use:                Initializes a hashtable with a given number of bins.  The
+ *             bins are initially empty.  The number of bins must be a power
+ *             of two.  This is not checked.
+ */
+
+void hash_create(hash_table *t, size_t n)
+{
+  hash_base **v;
+
+  t->a = arena_global;
+  t->v = x_alloc(t->a, n * sizeof(hash_base *));
+  t->mask = n - 1;
+  for (v = t->v; n; v++, n--)
+    *v = 0;
+}
+
+/* --- @hash_destroy@ --- *
+ *
+ * Arguments:  @hash_table *t@ = pointer to hashtable to destroy
+ *
+ * Returns:    ---
+ *
+ * Use:                Frees a hashtable.  The items are not freed: they are the
+ *             responsibility of the implementation.
+ */
+
+void hash_destroy(hash_table *t) { x_free(t->a, t->v); }
+
+/* --- @hash_bin@ --- *
+ *
+ * Arguments:  @hash_table *t@ = pointer to hashtable
+ *             @uint32 hash@ = hash value to look up
+ *
+ * Returns:    Pointer to the bin's list head.
+ *
+ * Use:                Given a hash value return the address of the appropriate list
+ *             head.  It is safe to remove the current entry from the table.
+ */
+
+hash_base **hash_bin(hash_table *t, uint32 hash)
+  { return (HASH_BIN(t, hash)); }
+
+/* --- @hash_extend@ --- *
+ *
+ * Arguments:  @hash_table *t@ = pointer to hashtable to extend
+ *
+ * Returns:    Nonzero if extension was successful.
+ *
+ * Use:                Extends a hashtable.  The number of bins is doubled and the
+ *             entries are redistributed.
+ */
+
+int hash_extend(hash_table *t)
+{
+  hash_base **v;
+  uint32 m = t->mask + 1;
+  size_t i;
+
+  /* --- Allocate a new hash bin vector --- */
+
+  if ((v = A_REALLOC(t->a, t->v,
+                    2 * m * sizeof(hash_base *),
+                    m * sizeof(hash_base *))) == 0) {
+    return (0);
+  }
+  t->v = v;
+  t->mask = (m * 2) - 1;
+
+  /* --- Wander through the table rehashing things --- */
+
+  for (i = 0; i < m; i++) {
+    hash_base **p = v + i;
+    hash_base **q = p + m;
+
+    while (*p) {
+      if (!((*p)->hash & m))
+       p = &(*p)->next;
+      else {
+       *q = *p;
+       q = &(*q)->next;
+       *p = (*p)->next;
+      }
+    }
+    *p = 0;
+    *q = 0;
+  }
+
+  return (1);
+}
+
+/* --- @hash_remove@ --- *
+ *
+ * Arguments:  @hash_table *t@ = pointer to hashtable
+ *             @hash_base *p@ = pointer to item to remove
+ *
+ * Returns:    ---
+ *
+ * Use:                Removes an item from a hashtable.  The item itself is not
+ *             freed, although it is removed from the data structure and is
+ *             safe to free.
+ */
+
+void hash_remove(hash_table *t, hash_base *p)
+{
+  hash_base **b = HASH_BIN(t, p->hash);
+  while (*b) {
+    if (*b == p) {
+      *b = p->next;
+      return;
+    }
+    b = &(*b)->next;
+  }
+  return;
+}
+
+/* --- @hash_mkiter@ --- *
+ *
+ * Arguments:  @hash_iter *i@ = pointer to iterator to create
+ *             @hash_table *t@ = pointer to hashtable to iterate
+ *
+ * Returns:    ---
+ *
+ * Use:                Initializes a hashtable iterator.
+ */
+
+void hash_mkiter(hash_iter *i, hash_table *t) { HASH_MKITER(i, t); }
+
+/* --- @hash_next@ --- *
+ *
+ * Arguments:  @hash_iter *i@ = pointer to iterator
+ *
+ * Returns:    Pointer to the next hashtable entry found, or null.
+ *
+ * Use:                Returns the next entry from the hashtable.
+ */
+
+hash_base *hash_next(hash_iter *i)
+{
+  hash_base *b;
+  HASH_NEXT(i, b);
+  return (b);
+}
+
+/*----- That's all, folks -------------------------------------------------*/
diff --git a/struct/hash.h b/struct/hash.h
new file mode 100644 (file)
index 0000000..b0aea23
--- /dev/null
@@ -0,0 +1,209 @@
+/* -*-c-*-
+ *
+ * General hashtable infrastructure
+ *
+ * (c) 1999 Straylight/Edgeware
+ */
+
+/*----- Licensing notice --------------------------------------------------*
+ *
+ * This file is part of the mLib utilities library.
+ *
+ * mLib is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * mLib is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with mLib; if not, write to the Free
+ * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+ * MA 02111-1307, USA.
+ */
+
+#ifndef MLIB_HASH_H
+#define MLIB_HASH_H
+
+#ifdef __cplusplus
+  extern "C" {
+#endif
+
+/*----- Notes -------------------------------------------------------------*
+ *
+ * This isn't a complete implementation of anything.  It's a collection of
+ * useful types and functions which will help when building hashtables of
+ * things.  The general-purpose hashtable is provided by the @sym@ functions.
+ */
+
+/*----- Header files ------------------------------------------------------*/
+
+#include <stddef.h>
+
+#ifndef MLIB_ARENA_H
+#  include "arena.h"
+#endif
+
+#ifndef MLIB_BITS_H
+#  include "bits.h"
+#endif
+
+/*----- Data structures ---------------------------------------------------*/
+
+/* --- Hashtable basics --- *
+ *
+ * This contains the bare minimum to actually get anything useful done.  In
+ * particular it doesn't maintain any weighting information: when to extend
+ * the table is left up to the particular implementation.
+ */
+
+typedef struct hash_table {
+  uint32 mask;                         /* Bit mask of hash bits */
+  struct hash_base **v;                        /* Vector of hash bins */
+  arena *a;                            /* Allocation arena */
+} hash_table;
+
+/* --- A hashtable entry --- *
+ *
+ * This is the bare minimum of what needs to be remembered in each hashtable
+ * entry.  Comparison of elements is left to the implementation, so I don't
+ * need to know anything about how to represent hash keys here.
+ */
+
+typedef struct hash_base {
+  struct hash_base *next;              /* Next entry in hash bin */
+  uint32 hash;                         /* Hash value for this entry */
+} hash_base;
+
+/* --- A hashtable iterator --- */
+
+typedef struct hash_iter {
+  hash_table *t;                       /* Hashtable being iterated */
+  hash_base *p;                                /* Address of next item to return */
+  size_t i;                            /* Index of next hash bin to use */
+} hash_iter;
+
+/*----- Functions provided ------------------------------------------------*/
+
+/* --- @hash_create@ --- *
+ *
+ * Arguments:  @hash_table *t@ = pointer to hashtable to initialize
+ *             @size_t n@ = number of bins to allocate (zero initially)
+ *
+ * Returns:    ---
+ *
+ * Use:                Initializes a hashtable with a given number of bins.  The
+ *             bins are initially empty.  The number of bins must be a power
+ *             of two.  This is not checked.
+ */
+
+extern void hash_create(hash_table */*t*/, size_t /*n*/);
+
+/* --- @hash_destroy@ --- *
+ *
+ * Arguments:  @hash_table *t@ = pointer to hashtable to destroy
+ *
+ * Returns:    ---
+ *
+ * Use:                Frees a hashtable.  The items are not freed: they are the
+ *             responsibility of the implementation.
+ */
+
+extern void hash_destroy(hash_table */*t*/);
+
+/* --- @hash_bin@ --- *
+ *
+ * Arguments:  @hash_table *t@ = pointer to hashtable
+ *             @uint32 hash@ = hash value to look up
+ *
+ * Returns:    Pointer to the bin's list head.
+ *
+ * Use:                Given a hash value return the address of the appropriate list
+ *             head.  It is safe to remove the current entry from the table.
+ */
+
+#define HASH_BIN(t, hash) ((t)->v + ((hash) & (t)->mask))
+
+extern hash_base **hash_bin(hash_table */*t*/, uint32 /*hash*/);
+
+/* --- @hash_extend@ --- *
+ *
+ * Arguments:  @hash_table *t@ = pointer to hashtable to extend
+ *
+ * Returns:    Nonzero if extension was successful.
+ *
+ * Use:                Extends a hashtable.  The number of bins is doubled and the
+ *             entries are redistributed.
+ */
+
+extern int hash_extend(hash_table */*t*/);
+
+/* --- @hash_remove@ --- *
+ *
+ * Arguments:  @hash_table *t@ = pointer to hashtable
+ *             @hash_base *p@ = pointer to item to remove
+ *
+ * Returns:    ---
+ *
+ * Use:                Removes an item from a hashtable.  The item itself is not
+ *             freed, although it is removed from the data structure and is
+ *             safe to free.
+ */
+
+extern void hash_remove(hash_table */*t*/, hash_base */*p*/);
+
+/* --- @hash_mkiter@ --- *
+ *
+ * Arguments:  @hash_iter *i@ = pointer to iterator to create
+ *             @hash_table *t@ = pointer to hashtable to iterate
+ *
+ * Returns:    ---
+ *
+ * Use:                Initializes a hashtable iterator.
+ */
+
+#define HASH_MKITER(i_, t_) ((i_)->t = (t_), (i_)->p = 0, (i_)->i = 0)
+
+extern void hash_mkiter(hash_iter */*i*/, hash_table */*t*/);
+
+/* --- @hash_next@ --- *
+ *
+ * Arguments:  @hash_iter *i@ = pointer to iterator
+ *
+ * Returns:    Pointer to the next hashtable entry found, or null.
+ *
+ * Use:                Returns the next entry from the hashtable.
+ */
+
+#define HASH_NEXT(i_, b_) do {                                         \
+  hash_iter *_i = (i_);                                                        \
+  hash_base *_p;                                                       \
+  hash_table *_t = _i->t;                                              \
+  uint32 _m = _t->mask;                                                        \
+                                                                       \
+  for (;;) {                                                           \
+    if (_i->p) {                                                       \
+      _p = _i->p;                                                      \
+      _i->p = _p->next;                                                        \
+      break;                                                           \
+    } else if (_i->i > _m) {                                           \
+      _p = 0;                                                          \
+      break;                                                           \
+    } else                                                             \
+      _i->p = _t->v[_i->i++];                                          \
+  }                                                                    \
+  (b_) = _p;                                                           \
+} while (0)
+
+extern hash_base *hash_next(hash_iter */*i*/);
+
+/*----- That's all, folks -------------------------------------------------*/
+
+#ifdef __cplusplus
+  }
+#endif
+
+#endif
diff --git a/struct/sym.3 b/struct/sym.3
new file mode 100644 (file)
index 0000000..80cb913
--- /dev/null
@@ -0,0 +1,238 @@
+.\" -*-nroff-*-
+.de VS
+.sp 1
+.RS
+.nf
+.ft B
+..
+.de VE
+.ft R
+.fi
+.RE
+.sp 1
+..
+.TH sym 3 "8 May 1999" "Straylight/Edgeware" "mLib utilities library"
+.SH NAME
+sym \- symbol table manager
+.\" @sym_create
+.\" @sym_destroy
+.\" @sym_find
+.\" @sym_remove
+.\" @sym_mkiter
+.\" @sym_next
+.\"
+.\" @SYM_NAME
+.\" @SYM_LEN
+.\" @SYM_HASH
+.\"
+.SH SYNOPSIS
+.nf
+.B "#include <mLib/sym.h>"
+
+.BI "void sym_create(sym_table *" t );
+.BI "void sym_destroy(sym_table *" t );
+
+.BI "void *sym_find(sym_table *" t ,
+.BI "               const char *" n ", long " l ,
+.BI "               size_t " sz ", unsigned *" f );
+.BI "void sym_remove(sym_table *" t ", void *" b );
+
+.BI "const char *SYM_NAME(const void *" p );
+.BI "size_t SYM_LEN(const void *" p );
+.BI "uint32 SYM_HASH(const void *" p );
+
+.BI "void sym_mkiter(sym_iter *" i ", sym_table *" t );
+.BI "void *sym_next(sym_iter *" i );
+.fi
+.SH "DESCRIPTION"
+The
+.B sym
+functions implement a data structure often described as a dictionary, a
+finite map, an associative array, or a symbol table.  It associates
+.I values
+with
+.I keys
+such that the value corresponding to a given key can be found quickly.
+Additionally, all stored associations can be enumerated.
+.PP
+The interface provides an
+.I intrusive
+symbol table.  The data objects stored in the table must include a small
+header used by the symbol table manager.  This reduces the amount of
+pointer fiddling that needs to be done, and in practice doesn't seem to
+be much of a problem.  It's also fairly easy to construct a
+non-intrusive interface if you really want one.
+.PP
+There are three main data structures involved in the interface:
+.TP
+.B sym_table
+Keeps track of the information associated with a particular table.
+.TP
+.B sym_base
+The header which must be attached to the front of all the value
+objects.
+.TP
+.B sym_iter
+An iterator object, used for enumerating all of the associations stored
+in a symbol table.
+.PP
+All of the above data structures should be considered
+.IR opaque :
+don't try looking inside.  Representations have changed in the past, and
+they may change again in the future.
+.SS "Creation and destruction"
+The
+.B sym_table
+object itself needs to be allocated by the caller.  It is initialized by
+passing it to the function
+.BR sym_create .
+After initialization, the table contains no entries.
+.PP
+Initializing a symbol table involves allocating some memory.  If this
+allocation fails, an
+.B EXC_NOMEM
+exception is raised.
+.PP
+When a symbol table is no longer needed, the memory occupied by the
+values and other maintenance structures can be reclaimed by calling
+.BR sym_destroy .
+Any bits of user data attached to values should previously have been
+destroyed.
+.SS "Adding, searching and removing"
+Most of the actual work is done by the function
+.BR sym_find .
+It does both lookup and creation, depending on its arguments.  To do its
+job, it needs to know the following bits of information:
+.TP
+.BI "sym_table *" t
+A pointer to a symbol table to manipulate.
+.TP
+.BI "const char *" n
+The address of the
+.I key
+to look up or create.  Usually this will be a simple text string,
+although it can actually be any arbitrary binary data.
+.TP
+.BI "long " l
+The length of the key.  If this is \-1,
+.B sym_find
+assumes that the key is a null-terminated string, and calculates its
+length itself.  This is entirely equivalent to passing
+.BI strlen( n )\fR.
+.TP
+.BI "size_t " sz
+The size of the value block to allocate if the key could not be found.
+If this is zero, no value is allocated, and a null pointer is returned
+to indicate an unsuccessful lookup.
+.TP
+.BI "unsigned *" f
+The address of a `found' flag to set.  This is an output parameter.  On
+exit,
+.B sym_find
+will set the value of
+.BI * f
+to zero if the key could not be found, or nonzero if it was found.  This
+can be used to tell whether the value returned has been newly allocated,
+or whether it was already in the table.
+.PP
+A terminating null byte is appended to the copy of the symbol's name in
+memory.  This is not considered to be a part of the symbol's name, and
+does not contribute to the name's length as reported by the
+.B SYM_LEN
+macro.
+.PP
+A symbol can be removed from the table by calling
+.BR sym_remove ,
+passing the symbol table itself, and the value block that needs
+removing.
+.SS "Enquiries about symbols"
+Three macros are provided to enable simple enquiries about a symbol.
+Given a pointer
+.I s
+to a symbol table entry,
+.BI SYM_LEN( s )
+returns the length of the symbol's name (excluding any terminating null
+byte);
+.BI SYM_NAME( s )
+returns a pointer to the symbol's name; and
+.BI SYM_HASH( s )
+returns the symbol's hash value.
+.SS "Enumerating symbols"
+Enumerating the values in a symbol table is fairly simple.  Allocate a
+.B sym_iter
+object from somewhere.  Attach it to a symbol table by calling
+.BR sym_mkiter ,
+and passing in the addresses of the iterator and the symbol table.
+Then, each call to
+.B sym_next
+will return a different value from the symbol table, until all of them
+have been enumerated, at which point,
+.B sym_next
+returns a null pointer.
+.PP
+It's safe to remove the symbol you've just been returned by
+.BR sym_next .
+However, it's not safe to remove any other symbol.  So don't do that.
+.PP
+When you've finished with an iterator, it's safe to just throw it away.
+You don't need to call any functions beforehand.
+.SS "Use in practice"
+In normal use, the keys are simple strings (usually identifiers from
+some language), and the values are nontrivial structures providing
+information about types and values.
+.PP
+In this case, you'd define something like the following structure for
+your values:
+.VS
+typedef struct val {
+  sym_base _base;      /* Symbol header */
+  unsigned type;       /* Type of this symbol */
+  int dispoff;         /* Which display variable is in */
+  size_t frameoff;     /* Offset of variable in frame */
+} val;
+.VE
+Given a pointer
+.I v
+to a
+.BR val ,
+you can find the variable's name by calling
+.BI SYM_NAME( v )\fR.
+.PP
+You can look up a name in the table by saying something like:
+.VS
+val *v = sym_find(t, name, -1, 0, 0);
+if (!v)
+  error("unknown variable `%s'", name);
+.VE
+You can add in a new variable by saying something like
+.VS
+unsigned f;
+val *v = sym_find(t, name, -1, sizeof(val), &f);
+if (f)
+  error("variable `%s' already exists", name);
+/* fill in v */
+.VE
+You can examine all the variables in your symbol table by saying
+something like:
+.VS
+sym_iter i;
+val *v;
+
+for (sym_mkiter(&i, t); (v = sym_next(&i)) != 0; ) {
+  /* ... */
+}
+.VE
+That ought to be enough examples to be getting on with.
+.SS Implementation
+The symbol table is an extensible hashtable, using the universal hash
+function described in
+.BR unihash (3)
+and the global hashing key.  The hash chains are kept very short
+(probably too short, actually).  Every time a symbol is found, its block
+is promoted to the front of its bin chain so it gets found faster next
+time.
+.SH SEE ALSO
+.BR hash (3),
+.BR mLib (3).
+.SH AUTHOR
+Mark Wooding, <mdw@distorted.org.uk>
diff --git a/struct/sym.c b/struct/sym.c
new file mode 100644 (file)
index 0000000..2eb309c
--- /dev/null
@@ -0,0 +1,241 @@
+/* -*-c-*-
+ *
+ * Symbol table management
+ *
+ * (c) 1998 Straylight/Edgeware
+ */
+
+/*----- Licensing notice --------------------------------------------------*
+ *
+ * This file is part of the mLib utilities library.
+ *
+ * mLib is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * mLib is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with mLib; if not, write to the Free
+ * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+ * MA 02111-1307, USA.
+ */
+
+/*----- Header files ------------------------------------------------------*/
+
+/* --- ANSI headers --- */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+/* --- Local headers --- */
+
+#include "alloc.h"
+#include "arena.h"
+#include "bits.h"
+#include "exc.h"
+#include "hash.h"
+#include "macros.h"
+#include "sub.h"
+#include "sym.h"
+#include "unihash.h"
+
+/*----- Main code ---------------------------------------------------------*/
+
+/* --- @sym_create@ --- *
+ *
+ * Arguments:  @sym_table *t@ = symbol table to initialize
+ *
+ * Returns:    ---
+ *
+ * Use:                Initializes the given symbol table.  Raises @EXC_NOMEM@ if
+ *             there isn't enough memory.
+ */
+
+void sym_create(sym_table *t)
+{
+  hash_create(&t->t, SYM_INITSZ);
+  t->s = &sub_global;
+  t->load = SYM_LIMIT(SYM_INITSZ);
+}
+
+/* --- @sym_destroy@ --- *
+ *
+ * Arguments:  @sym_table *t@ = pointer to symbol table in question
+ *
+ * Returns:    ---
+ *
+ * Use:                Destroys a symbol table, freeing all the memory it used to
+ *             occupy.
+ */
+
+void sym_destroy(sym_table *t)
+{
+  sym_iter i;
+
+  SYM_MKITER(&i, t);
+  for (;;) {
+    sym_base *p;
+    SYM_NEXT(&i, p);
+    if (!p)
+      break;
+    x_free(t->t.a, p);
+  }
+  hash_destroy(&t->t);
+}
+
+/* --- @sym_find@ --- *
+ *
+ * Arguments:  @sym_table *t@ = pointer to symbol table in question
+ *             @const char *n@ = pointer to symbol name to look up
+ *             @long l@ = length of the name string or negative to measure
+ *             @size_t sz@ = size of desired symbol object, or zero
+ *             @unsigned *f@ = pointer to a flag, or null.
+ *
+ * Returns:    The address of a @sym_base@ structure, or null if not found
+ *             and @sz@ is zero.
+ *
+ * Use:                Looks up a symbol in a given symbol table.  The name is
+ *             passed by the address of its first character.  The length
+ *             may be given, in which case the name may contain arbitrary
+ *             binary data, or it may be given as a negative number, in
+ *             which case the length of the name is calculated as
+ *             @strlen(n) + 1@.
+ *
+ *             The return value is the address of a pointer to a @sym_base@
+ *             block (which may have other things on the end, as above).  If
+ *             the symbol could be found, the return value points to the
+ *             symbol block.  If the symbol wasn't there, then if @sz@ is
+ *             nonzero, a new symbol is created and its address is returned;
+ *             otherwise a null pointer is returned.  The exception
+ *             @EXC_NOMEM@ is raised if the block can't be allocated.
+ *
+ *             The value of @*f@ indicates whether a new symbol entry was
+ *             created: a nonzero value indicates that an old value was
+ *             found.
+ */
+
+void *sym_find(sym_table *t, const char *n, long l, size_t sz, unsigned *f)
+{
+  uint32 hash;
+  size_t len = 0;
+  hash_base **bin, **p;
+  sym_base *q;
+
+  /* --- Find the correct bin --- */
+
+  len = l < 0 ? strlen(n) : l;
+  hash = UNIHASH(&unihash_global, n, len);
+  bin = HASH_BIN(&t->t, hash);
+
+  /* --- Search the bin list --- */
+
+  for (p = bin; *p; p = &(*p)->next) {
+    q = (sym_base *)*p;
+    if (hash == q->b.hash && len == q->len &&
+       MEMCMP(n, ==, SYM_NAME(q), len)) {
+
+      /* --- Found a match --- *
+       *
+       * As a minor, and probably pointless, tweak, move the item to the
+       * front of its bin list.
+       */
+
+      (*p) = q->b.next;
+      q->b.next = *bin;
+      *bin = &q->b;
+
+      /* --- Return the block --- */
+
+      if (f) *f = 1;
+      return (q);
+    }
+  }
+
+  /* --- Couldn't find the item there --- */
+
+  if (f) *f = 0;
+  if (!sz) return (0);
+
+  /* --- Create a new symbol block and initialize it --- *
+   *
+   * The name is attached to the end of the symbol block.
+   */
+
+  q = x_alloc(t->t.a, sz + len + 1);
+  q->b.next = *bin;
+  q->b.hash = hash;
+  q->name = (char *)q + sz;
+  memcpy(q->name, n, len);
+  q->name[len] = 0;
+  q->len = len;
+  *bin = &q->b;
+
+  /* --- Consider growing the array --- */
+
+  if (t->load)
+    t->load--;
+  if (!t->load && hash_extend(&t->t))
+    t->load = SYM_LIMIT(t->t.mask + 1);
+
+  /* --- Finished that, so return the new symbol block --- */
+
+  return (q);
+}
+
+/* --- @sym_remove@ --- *
+ *
+ * Arguments:  @sym_table *t@ = pointer to a symbol table object
+ *             @void *p@ = pointer to symbol table entry
+ *
+ * Returns:    ---
+ *
+ * Use:                Removes the object from the symbol table.  The space occupied
+ *             by the object and its name is freed; anything else attached
+ *             to the entry should already be gone by this point.
+ */
+
+void sym_remove(sym_table *t, void *p)
+{
+  sym_base *q = p;
+  hash_remove(&t->t, &q->b);
+  xfree(q);
+  t->load++;
+}
+
+/* --- @sym_mkiter@ --- *
+ *
+ * Arguments:  @sym_iter *i@ = pointer to an iterator object
+ *             @sym_table *t@ = pointer to a symbol table object
+ *
+ * Returns:    ---
+ *
+ * Use:                Creates a new symbol table iterator which may be used to
+ *             iterate through a symbol table.
+ */
+
+void sym_mkiter(sym_iter *i, sym_table *t) { SYM_MKITER(i, t); }
+
+/* --- @sym_next@ --- *
+ *
+ * Arguments:  @sym_iter *i@ = pointer to iterator object
+ *
+ * Returns:    Pointer to the next symbol found, or null when finished.
+ *
+ * Use:                Returns the next symbol from the table.  Symbols are not
+ *             returned in any particular order.
+ */
+
+void *sym_next(sym_iter *i)
+{
+  void *p;
+  SYM_NEXT(i, p);
+  return (p);
+}
+
+/*----- That's all, folks -------------------------------------------------*/
diff --git a/struct/sym.h b/struct/sym.h
new file mode 100644 (file)
index 0000000..9e191eb
--- /dev/null
@@ -0,0 +1,229 @@
+/* -*-c-*-
+ *
+ * Symbol table management
+ *
+ * (c) 1998 Straylight/Edgeware
+ */
+
+/*----- Licensing notice --------------------------------------------------*
+ *
+ * This file is part of the mLib utilities library.
+ *
+ * mLib is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * mLib is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with mLib; if not, write to the Free
+ * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+ * MA 02111-1307, USA.
+ */
+
+#ifndef MLIB_SYM_H
+#define MLIB_SYM_H
+
+#ifdef __cplusplus
+  extern "C" {
+#endif
+
+/*----- Required headers --------------------------------------------------*/
+
+#include <stddef.h>
+
+#ifndef MLIB_BITS_H
+#  include "bits.h"
+#endif
+
+#ifndef MLIB_HASH_H
+#  include "hash.h"
+#endif
+
+#ifndef MLIB_SUB_H
+#  include "sub.h"
+#endif
+
+/*----- Tuning parameters -------------------------------------------------*/
+
+/* --- Initial hash table size --- *
+ *
+ * This is the initial @mask@ value.  It must be of the form %$2^n - 1$%,
+ * so that it can be used to mask of the bottom bits of a hash value.
+ */
+
+#define SYM_INITSZ 32                  /* Size of a new hash table */
+
+/* --- Maximum load factor --- *
+ *
+ * This parameter controls how much the table has to be loaded before the
+ * table is extended.  The number of elements %$n$%, the number of bins %$b$%
+ * and the limit %$l$% satisfy the relation %$n < bl$%; if a new item is
+ * added to the table and this relation is found to be false, the table is
+ * doubled in size.
+ */
+
+#define SYM_LIMIT(n) ((n) * 2)         /* Load factor for growing table */
+
+/*----- Type definitions --------------------------------------------------*/
+
+/* --- Symbol table --- *
+ *
+ * A @sym_table@ contains the information needed to manage a symbol table.
+ * Users shouldn't fiddle with this information directly, but it needs to be
+ * here so that objects of the correct type can be created.
+ */
+
+typedef struct sym_table {
+  hash_table t;
+  subarena *s;
+  size_t load;
+} sym_table;
+
+/* --- A symbol table entry --- *
+ *
+ * I don't care what actually gets stored in symbol entries because I don't
+ * create them: that's the responsibility of my client.  All I care about
+ * here is that whatever gets passed to me is a structure whose first member
+ * is a @sym_base@.  The ANSI guarantees about structure layout are
+ * sufficient to allow me to manipulate such objects.
+ */
+
+typedef struct sym_base {
+  hash_base b;                         /* Base structure */
+  char *name;                          /* Pointer to name string */
+  size_t len;                          /* Length of the symbol's name */
+} sym_base;
+
+/* --- Macros for picking out useful information --- *
+ *
+ * Note that @SYM_LEN@ returns the size of the symbol key.  For textual keys,
+ * this will include the terminating null.
+ */
+
+#define SYM_NAME(sy) ((const char *)(((sym_base *)(sy))->name))
+#define SYM_LEN(sy) (((sym_base *)(sy))->len + 0)
+#define SYM_HASH(sy) (((sym_base *)(sy))->b.hash + 0)
+
+/* --- An iterator block --- */
+
+typedef struct { hash_iter i; } sym_iter;
+
+/*----- External functions ------------------------------------------------*/
+
+/* --- @sym_create@ --- *
+ *
+ * Arguments:  @sym_table *t@ = symbol table to initialize
+ *
+ * Returns:    ---
+ *
+ * Use:                Initializes the given symbol table.  Raises @EXC_NOMEM@ if
+ *             there isn't enough memory.
+ */
+
+extern void sym_create(sym_table */*t*/);
+
+/* --- @sym_destroy@ --- *
+ *
+ * Arguments:  @sym_table *t@ = pointer to symbol table in question
+ *
+ * Returns:    ---
+ *
+ * Use:                Destroys a symbol table, freeing all the memory it used to
+ *             occupy.
+ */
+
+extern void sym_destroy(sym_table */*t*/);
+
+/* --- @sym_find@ --- *
+ *
+ * Arguments:  @sym_table *t@ = pointer to symbol table in question
+ *             @const char *n@ = pointer to symbol name to look up
+ *             @long l@ = length of the name string or negative to measure
+ *             @size_t sz@ = size of desired symbol object, or zero
+ *             @unsigned *f@ = pointer to a flag, or null.
+ *
+ * Returns:    The address of a @sym_base@ structure, or null if not found
+ *             and @sz@ is zero.
+ *
+ * Use:                Looks up a symbol in a given symbol table.  The name is
+ *             passed by the address of its first character.  The length
+ *             may be given, in which case the name may contain arbitrary
+ *             binary data, or it may be given as a negative number, in
+ *             which case the length of the name is calculated as
+ *             @strlen(n) + 1@.
+ *
+ *             The return value is the address of a pointer to a @sym_base@
+ *             block (which may have other things on the end, as above).  If
+ *             the symbol could be found, the return value points to the
+ *             symbol block.  If the symbol wasn't there, then if @sz@ is
+ *             nonzero, a new symbol is created and its address is returned;
+ *             otherwise a null pointer is returned.  The exception
+ *             @EXC_NOMEM@ is raised if the block can't be allocated.
+ *
+ *             The value of @*f@ indicates whether a new symbol entry was
+ *             created: a nonzero value indicates that an old value was
+ *             found.
+ */
+
+extern void *sym_find(sym_table */*t*/, const char */*n*/, long /*l*/,
+                     size_t /*sz*/, unsigned */*f*/);
+
+/* --- @sym_remove@ --- *
+ *
+ * Arguments:  @sym_table *t@ = pointer to a symbol table object
+ *             @void *b@ = pointer to symbol table entry
+ *
+ * Returns:    ---
+ *
+ * Use:                Removes the object from the symbol table.  The space occupied
+ *             by the object and its name is freed; anything else attached
+ *             to the entry should already be gone by this point.
+ */
+
+extern void sym_remove(sym_table */*t*/, void */*b*/);
+
+/* --- @sym_mkiter@ --- *
+ *
+ * Arguments:  @sym_iter *i@ = pointer to an iterator object
+ *             @sym_table *t@ = pointer to a symbol table object
+ *
+ * Returns:    ---
+ *
+ * Use:                Creates a new symbol table iterator which may be used to
+ *             iterate through a symbol table.
+ */
+
+#define SYM_MKITER(i_, t_) HASH_MKITER(&(i_)->i, &(t_)->t)
+
+extern void sym_mkiter(sym_iter */*i*/, sym_table */*t*/);
+
+/* --- @sym_next@ --- *
+ *
+ * Arguments:  @sym_iter *i@ = pointer to iterator object
+ *
+ * Returns:    Pointer to the next symbol found, or null when finished.
+ *
+ * Use:                Returns the next symbol from the table.  Symbols are not
+ *             returned in any particular order.
+ */
+
+#define SYM_NEXT(i_, p) do {                                           \
+  hash_base *_q;                                                       \
+  HASH_NEXT(&(i_)->i, _q);                                             \
+  (p) = (void *)_q;                                                    \
+} while (0)
+
+extern void *sym_next(sym_iter */*i*/);
+
+/*----- That's all, folks -------------------------------------------------*/
+
+#ifdef __cplusplus
+  }
+#endif
+
+#endif
diff --git a/struct/t/assoc-test.c b/struct/t/assoc-test.c
new file mode 100644 (file)
index 0000000..a727bbf
--- /dev/null
@@ -0,0 +1,98 @@
+#include <assert.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "assoc.h"
+#include "atom.h"
+#include "macros.h"
+
+typedef struct word {
+  assoc_base _b;
+  int i;
+} word;
+
+static int cmp(const void *a, const void *b)
+{
+  const word *const *v = b;
+  const word *const *w = a;
+  return (strcmp(ATOM_NAME(ASSOC_ATOM(*w)), ATOM_NAME(ASSOC_ATOM(*v))));
+}
+
+int main(void)
+{
+  char buf[256];
+  char *p;
+  atom_table at;
+  assoc_table t;
+  size_t n = 0, j;
+
+  atom_createtable(&at);
+  assoc_create(&t);
+
+  while (fgets(buf, sizeof(buf), stdin)) {
+/*     printf("+++ %s", buf); */
+    buf[strlen(buf) - 1] = 0;
+    p = strtok(buf, " ");
+
+    if (STRCMP(p, ==, "set")) {
+      char *k = strtok(0, " ");
+      int i = atoi(strtok(0, " "));
+      unsigned f;
+      word *w = assoc_find(&t, atom_intern(&at, k), sizeof(word), &f);
+      w->i = i;
+      if (!f)
+       n++;
+    } else if (STRCMP(p, ==, "get")) {
+      char *k = strtok(0, " ");
+      word *w = assoc_find(&t, atom_intern(&at, k), 0, 0);
+      if (w)
+       printf("%i\n", w->i);
+      else
+       puts("*MISSING*");
+    } else if (STRCMP(p, ==, "del")) {
+      char *k = strtok(0, " ");
+      word *w = assoc_find(&t, atom_intern(&at, k), 0, 0);
+      if (w) {
+       assoc_remove(&t, w);
+       n--;
+      } else
+       puts("*MISSING*");
+    } else if (STRCMP(p, ==, "count")) {
+      printf("%lu\n", (unsigned long)n);
+    } else if (STRCMP(p, ==, "show")) {
+      assoc_iter i;
+      word *w;
+      word **v, **vv;
+
+      if (!n)
+       puts("*EMPTY*");
+      else {
+       v = malloc(n * sizeof(*v));
+       if (!v) {
+         puts("*NOMEM*");
+         continue;
+       }
+       for (vv = v, assoc_mkiter(&i, &t), j = 0;
+            (w = assoc_next(&i)) != 0;
+            vv++, j++) {
+         assert(j < n);
+         *vv = w;
+       }
+       assert(j == n);
+       qsort(v, n, sizeof(*v), cmp);
+       printf("%s:%i", ATOM_NAME(ASSOC_ATOM(*v)), (*v)->i);
+       for (vv = v + 1; --j; vv++)
+         printf(" %s:%i", ATOM_NAME(ASSOC_ATOM(*vv)), (*vv)->i);
+       free(v);
+       putchar('\n');
+      }
+    } else
+      puts("*BAD*");
+/*     printf("--- %d\n", n); */
+  }
+
+  assoc_destroy(&t);
+  atom_destroytable(&at);
+  return (0);
+}
diff --git a/struct/t/da-gtest.py b/struct/t/da-gtest.py
new file mode 100644 (file)
index 0000000..49c26dc
--- /dev/null
@@ -0,0 +1,182 @@
+#! /usr/bin/python
+### -*-python-*-
+###
+### Generate input script and expected output for dynamic array testing.
+
+import sys as SYS
+import random as R
+
+if SYS.version_info >= (3,): xrange = range
+
+###--------------------------------------------------------------------------
+### Command-line parsing.
+
+SYS.argv[0:1] = []
+def arg(default = None):
+  if len(SYS.argv):
+    r = SYS.argv[0]
+    SYS.argv[0:1] = []
+    return r
+  else:
+    return default
+
+R.seed(None)
+SEED = int(arg(str(R.randrange(0, 1 << 32))), 0)
+R.seed(SEED)
+
+LINES = int(arg(1000))
+
+###--------------------------------------------------------------------------
+### Initialization.
+
+SERIAL = 1
+ARRAY = []
+
+SCRIPT = open('da.script', 'w')
+WIN = open('expout', 'w')
+
+###--------------------------------------------------------------------------
+### Utility functions.
+
+OPS = []
+def op(weight):
+  """
+  Operation decorator.  Add the following function to the operations table,
+  with the given probability WEIGHT.  This works as follows: if TOTAL is the
+  total of all the WEIGHTs, then this operation has a probability of
+  WEIGHT/TOTAL of being selected.
+  """
+  def _(cls):
+    OPS.append((weight, cls))
+    return cls
+  return _
+
+def serial():
+  """Return the next number in a simple sequence."""
+  global SERIAL
+  SERIAL += 1
+  return SERIAL - 1
+
+def mkseq():
+  """Return a short list of stuff to be added to the array."""
+  seq = [serial()]
+  while R.randrange(0, 4) < 3: seq.append(serial())
+  return seq
+
+def mkseqlen():
+  """Return a length of stuff to be removed from the array."""
+  n = 1
+  while R.randrange(0, 4) < 3: n += 1
+  return n
+
+###--------------------------------------------------------------------------
+### The actual operations.
+
+@op(20)
+def op_push():
+  n = serial()
+  SCRIPT.write('push %d\n' % n)
+  ARRAY.append(n)
+@op(20)
+def op_pop():
+  SCRIPT.write('pop\n')
+  if not ARRAY:
+    WIN.write('*UFLOW*\n')
+  else:
+    n, = ARRAY[-1:]
+    ARRAY[-1:] = []
+    WIN.write('%d\n' % n)
+@op(20)
+def op_unshift():
+  n = serial()
+  SCRIPT.write('unshift %d\n' % n)
+  ARRAY[0:0] = [n]
+@op(20)
+def op_shift():
+  SCRIPT.write('shift\n')
+  if not ARRAY:
+    WIN.write('*UFLOW*\n')
+  else:
+    n = ARRAY[0]
+    ARRAY[0:1] = []
+    WIN.write('%d\n' % n)
+
+@op(10)
+def op_insert():
+  stuff = mkseq()
+  SCRIPT.write('insert ' + ' '.join(['%d' % i for i in stuff]) + '\n')
+  ARRAY[0:0] = stuff
+@op(10)
+def op_append():
+  global ARRAY                          # += is a binding occurrence
+  stuff = mkseq()
+  SCRIPT.write('append ' + ' '.join(['%d' % i for i in stuff]) + '\n')
+  ARRAY += stuff
+
+@op(20)
+def op_delete():
+  if len(ARRAY) < LINES/10: return
+  n = mkseqlen()
+  SCRIPT.write('delete %d\n' % n)
+  if n > len(ARRAY): WIN.write('*UFLOW*\n')
+  else: ARRAY[0:n] = []
+@op(20)
+def op_reduce():
+  if len(ARRAY) < LINES/10: return
+  n = mkseqlen()
+  SCRIPT.write('reduce %d\n' % n)
+  if n > len(ARRAY): WIN.write('*UFLOW*\n')
+  else: ARRAY[-n:] = []
+
+def mkindex():
+  if not ARRAY: ix = 0
+  else: ix = R.randrange(0, len(ARRAY))
+  while R.randrange(0, 2) < 1: ix += 1
+  return ix
+
+@op(20)
+def op_set():
+  global ARRAY                          # += is a binding occurrence
+  ix, x = mkindex(), serial()
+  SCRIPT.write('set %d %d\n' % (ix, x))
+  if ix >= len(ARRAY): ARRAY += [-1] * (ix - len(ARRAY) + 1)
+  ARRAY[ix] = x
+@op(20)
+def op_get():
+  ix = mkindex()
+  SCRIPT.write('get %d\n' % ix)
+  if ix >= len(ARRAY): WIN.write('*RANGE*\n')
+  else: WIN.write('%d\n' % ARRAY[ix])
+
+@op(10)
+def op_first():
+  SCRIPT.write('first\n')
+  if len(ARRAY): WIN.write('%d\n' % ARRAY[0])
+  else: WIN.write('*RANGE*\n')
+@op(10)
+def op_last():
+  SCRIPT.write('last\n')
+  if len(ARRAY): WIN.write('%d\n' % ARRAY[-1])
+  else: WIN.write('*RANGE*\n')
+
+@op(1)
+def op_show():
+  SCRIPT.write('show\n')
+  if not ARRAY: WIN.write('*EMPTY*\n')
+  else: WIN.write(' '.join(['%d' % i for i in ARRAY]) + '\n')
+
+###--------------------------------------------------------------------------
+### Generate the output.
+
+OPTAB = []
+for p, func in OPS:
+  OPTAB += [func] * p
+for i in xrange(LINES):
+  OPTAB[R.randrange(0, len(OPTAB))]()
+op_show()
+
+SCRIPT.close()
+WIN.close()
+open('da.seed', 'w').write('da-gtest seed = %08x\n' % SEED)
+
+###----- That's all, folks --------------------------------------------------
diff --git a/struct/t/da-test.c b/struct/t/da-test.c
new file mode 100644 (file)
index 0000000..ba86a8d
--- /dev/null
@@ -0,0 +1,148 @@
+/* -*-c-*-
+ *
+ * Test driver for darray.
+ */
+
+#include <assert.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "darray.h"
+#include "exc.h"
+#include "macros.h"
+
+DA_DECL(vec, int);
+
+#ifdef notdef
+
+static void dump(vec *v)
+{
+  printf("# len = %lu, sz = %lu, off = %lu; push = %u, unshift = %u\n",
+        (unsigned long)v->b.len,
+        (unsigned long)v->b.sz,
+        (unsigned long)v->b.off,
+        v->b.push, v->b.unshift);
+  printf("# {");
+  if (DA_LEN(v)) {
+    size_t i;
+    printf(" %i", DA(v)[0]);
+    for (i = 1; i < DA_LEN(v); i++)
+      printf(", %i", DA(v)[i]);
+  }
+  puts(" }");
+  assert(v->b.sz + v->b.off == 128);
+}
+
+#endif
+
+int main(void)
+{
+  char buf[1024];
+  char *p;
+  vec v = DA_INIT;
+
+/*   setvbuf(stdout, 0, _IOLBF, BUFSIZ); */
+
+  while (fgets(buf, sizeof(buf), stdin)) {
+    buf[strlen(buf) - 1] = 0;
+/*     printf("# %s\n", buf); */
+    p = strtok(buf, " ");
+
+    TRY {
+      if (STRCMP(p, ==, "push")) {
+       int n = atoi(strtok(0, " "));
+       DA_PUSH(&v, n);
+      } else if (STRCMP(p, ==, "unshift")) {
+       int n = atoi(strtok(0, " "));
+       DA_UNSHIFT(&v, n);
+      } else if (STRCMP(p, ==, "pop")) {
+       printf("%i\n", DA_POP(&v));
+      } else if (STRCMP(p, ==, "shift")) {
+       printf("%i\n", DA_SHIFT(&v));
+      } else if (STRCMP(p, ==, "insert") ||
+                STRCMP(p, ==, "append")) {
+       vec vv;
+       char *q = p;
+       DA_CREATE(&vv);
+/*     putchar('#'); */
+       while ((p = strtok(0, " ")) != 0) {
+         int n = atoi(p);
+         DA_PUSH(&vv, n);
+       }
+       if (STRCMP(q, ==, "insert")) {
+         DA_SHUNT(&v, DA_LEN(&vv));
+         DA_SLIDE(&v, DA_LEN(&vv));
+         memcpy(DA(&v), DA(&vv), DA_LEN(&vv) * sizeof(int));
+       } else {
+         size_t l = DA_LEN(&v);
+         DA_ENSURE(&v, DA_LEN(&vv));
+         DA_EXTEND(&v, DA_LEN(&vv));
+         memcpy(DA(&v) + l, DA(&vv), DA_LEN(&vv) * sizeof(int));
+       }
+       DA_DESTROY(&vv);
+      } else if (STRCMP(p, ==, "delete")) {
+       int n = atoi(strtok(0, " "));
+       DA_UNSLIDE(&v, n);
+      } else if (STRCMP(p, ==, "reduce")) {
+       int n = atoi(strtok(0, " "));
+       DA_SHRINK(&v, n);
+      } else if (STRCMP(p, ==, "set")) {
+       size_t i = atoi(strtok(0, " "));
+       int n = atoi(strtok(0, " "));
+       size_t l = DA_LEN(&v);
+       DA_INCLUDE(&v, i);
+       if (i >= l) {
+         size_t j;
+         for (j = l; j < i; j++)
+           DA(&v)[j] = -1;
+       }
+       DA(&v)[i] = n;
+      } else if (STRCMP(p, ==, "get")) {
+       size_t i = atoi(strtok(0, " "));
+       if (i >= DA_LEN(&v))
+         puts("*RANGE*");
+       else
+         printf("%i\n", DA(&v)[i]);
+      } else if (STRCMP(p, ==, "first")) {
+       if (DA_LEN(&v))
+         printf("%i\n", DA_FIRST(&v));
+       else
+         puts("*RANGE*");
+      } else if (STRCMP(p, ==, "last")) {
+       if (DA_LEN(&v))
+         printf("%i\n", DA_LAST(&v));
+       else
+         puts("*RANGE*");
+      } else if (STRCMP(p, ==, "show")) {
+       if (DA_LEN(&v) == 0)
+         puts("*EMPTY*");
+       else {
+         size_t i;
+         printf("%i", DA(&v)[0]);
+         for (i = 1; i < DA_LEN(&v); i++)
+           printf(" %i", DA(&v)[i]);
+         putchar('\n');
+       }
+      } else
+       puts("*BAD*");
+    } CATCH switch (exc_type) {
+      case DAEXC_UFLOW:
+       puts("*UFLOW*");
+       break;
+      case DAEXC_OFLOW:
+       puts("*OFLOW*");
+       break;
+      case EXC_NOMEM:
+       puts("*NOMEM*");
+       break;
+      default:
+       puts("*UNKNOWN-EXCEPTION*");
+       break;
+    } END_TRY;
+/*     dump(&v); */
+  }
+
+  DA_DESTROY(&v);
+  return (0);
+}
diff --git a/struct/t/dstr-putf-test.c b/struct/t/dstr-putf-test.c
new file mode 100644 (file)
index 0000000..462371d
--- /dev/null
@@ -0,0 +1,94 @@
+#include "config.h"
+
+#include <assert.h>
+#include <float.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "dstr.h"
+#include "macros.h"
+
+static int win = 0, lose = 0;
+static dstr d = DSTR_INIT;
+static char buf[1024];
+
+static void check(const char *what, const char *want)
+{
+  if (STRCMP(want, ==, d.buf))
+    win++;
+  else {
+    lose++;
+    fprintf(stderr, "test failed: %s\n  expected: %s\n     found: %s\n",
+           what, want, d.buf);
+  }
+}
+
+static void PRINTF_LIKE(1, 2) format(const char *fmt, ...)
+{
+  va_list ap;
+  va_start(ap, fmt);
+  dstr_reset(&d);
+  dstr_vputf(&d, fmt, &ap);
+  va_end(ap);
+}
+
+static void PRINTF_LIKE(1, 2) prepare(const char *fmt, ...)
+{
+  va_list ap;
+  int n;
+
+  va_start(ap, fmt);
+#ifdef HAVE_SNPRINTF
+  n = vsnprintf(buf, sizeof(buf), fmt, ap);
+#else
+  n = vsprintf(buf, fmt, ap);
+#endif
+  assert(0 <= n && n < sizeof(buf));
+}
+
+#define TEST1(fmtargs) do {                                            \
+  format fmtargs;                                                      \
+  prepare fmtargs;                                                     \
+  check(#fmtargs, buf);                                                        \
+} while (0)
+
+#define TEST2(fmtargs, want) do {                                      \
+  format fmtargs;                                                      \
+  check(#fmtargs, want);                                               \
+} while (0)
+
+#define LENGTHY \
+  "This is a rather longer string than the code is expecting: will it fit?"
+
+int main(void)
+{
+  TEST2(("Hello, world!"), "Hello, world!");
+  TEST2(("just a ->%%<- sign"), "just a ->%<- sign");
+  TEST2(("Testing, testing, %d, %d, %d.", 1, 2, 3),
+       "Testing, testing, 1, 2, 3.");
+  TEST2(("->%5d<-", 138), "->  138<-");
+  TEST2(("->%*d<-", 5, 138), "->  138<-");
+  TEST2(("->%-*d<-", 5, 138), "->138  <-");
+  TEST2(("->%*d<-", -5, 138), "->138  <-");
+  TEST2(("->%-*d<-", -5, 138), "->138  <-");
+  TEST2(("->%.*s<-", 5, "truncate me"), "->trunc<-");
+  TEST2(("->%.*s<-", -5, "don't truncate me"), "->don't truncate me<-");
+  TEST2(("Truncation indirect: ->%.*s<-", 10, "a long string to be chopped"),
+       "Truncation indirect: ->a long str<-");
+  TEST2(("%08lx:%s", 0x65604204ul, "tripe-ec"), "65604204:tripe-ec");
+  TEST2(("%s", LENGTHY), LENGTHY);
+
+  TEST1(("big float: ->%f<- and integer %d\n", DBL_MAX, 42));
+
+  TEST2(("Testing, testing, %3$d, %2$d, %1$d.", 3, 2, 1),
+       "Testing, testing, 1, 2, 3.");
+  TEST2(("Truncation indirect: ->%1$.*2$s<-",
+        "a long string to be chopped", 10),
+       "Truncation indirect: ->a long str<-");
+
+  if (!lose) printf("All tests successful.\n");
+  else printf("FAILED %d of %d tests.\n", lose, win + lose);
+  return (!!lose);
+}
diff --git a/struct/t/sym-gtest.py b/struct/t/sym-gtest.py
new file mode 100644 (file)
index 0000000..381ea3a
--- /dev/null
@@ -0,0 +1,139 @@
+#! /usr/bin/python
+### -*-python-*-
+###
+### Generate input script and expected output for hash tables.
+
+import sys as SYS
+import random as R
+
+if SYS.version_info >= (3,): xrange = range
+
+###--------------------------------------------------------------------------
+### Command-line parsing.
+
+SYS.argv[0:1] = []
+def arg(default = None):
+  if len(SYS.argv):
+    r = SYS.argv[0]
+    SYS.argv[0:1] = []
+    return r
+  else:
+    return default
+
+R.seed(None)
+SEED = int(arg(str(R.randrange(0, 1 << 32))), 0)
+R.seed(SEED)
+
+LINES = int(arg(1000))
+
+###--------------------------------------------------------------------------
+### Word list.
+
+def word():
+  def char(): return 'abcdefghijklmnopqrstuvwxyz'[R.randrange(0, 26)]
+  word = char() + char() + char()
+  while R.randrange(0, 6) != 0: word += char()
+  return word
+
+## for i in ['/usr/share/dict/words', '/usr/dict/words']:
+##   try:
+##     WORDS = [line[:-1] for line in open(i)]
+##     raise Exception
+##   except:
+##     pass
+##   else:
+##     def word():
+##       return WORDS[R.randrange(0, len(WORDS))]
+##     break
+
+###--------------------------------------------------------------------------
+### Initialization.
+
+SERIAL = 1
+MAP = {}
+
+SCRIPT = open('sym.script', 'w')
+WIN = open('expout', 'w')
+
+###--------------------------------------------------------------------------
+### Utility functions.
+
+OPS = []
+def op(weight):
+  """
+  Operation decorator.  Add the following function to the operations table,
+  with the given probability WEIGHT.  This works as follows: if TOTAL is the
+  total of all the WEIGHTs, then this operation has a probability of
+  WEIGHT/TOTAL of being selected.
+  """
+  def _(cls):
+    OPS.append((weight, cls))
+    return cls
+  return _
+
+def serial():
+  """Return the next number in a simple sequence."""
+  global SERIAL
+  SERIAL += 1
+  return SERIAL - 1
+
+###--------------------------------------------------------------------------
+### The actual operations.
+
+@op(10)
+def op_set():
+  w = word()
+  n = serial()
+  SCRIPT.write('set %s %d\n' % (w, n))
+  MAP[w] = n
+
+@op(10)
+def op_get():
+  w = word()
+  try:
+    WIN.write('%d\n' % MAP[w])
+  except KeyError:
+    if R.randrange(8): return
+    WIN.write('*MISSING*\n')
+  SCRIPT.write('get %s\n' % (w))
+
+@op(10)
+def op_del():
+  w = word()
+  try:
+    del MAP[w]
+  except KeyError:
+    if R.randrange(8): return
+    WIN.write('*MISSING*\n')
+  SCRIPT.write('del %s\n' % (w))
+
+@op(4)
+def op_count():
+  SCRIPT.write('count\n')
+  WIN.write('%d\n' % len(MAP))
+
+@op(1)
+def op_show():
+  SCRIPT.write('show\n')
+  if not MAP:
+    WIN.write('*EMPTY*\n')
+  else:
+    kk = list(MAP.keys())
+    kk.sort()
+    WIN.write(' '.join(['%s:%d' % (k, MAP[k]) for k in kk]) + '\n')
+
+###--------------------------------------------------------------------------
+### Generate the output.
+
+OPTAB = []
+for p, func in OPS:
+  OPTAB += [func] * p
+for i in xrange(LINES):
+  OPTAB[R.randrange(0, len(OPTAB))]()
+op_show()
+
+SCRIPT.close()
+WIN.close()
+open('sym.seed', 'w').write('sym-gtest seed = %08x\n' % SEED)
+
+###----- That's all, folks --------------------------------------------------
diff --git a/struct/t/sym-test.c b/struct/t/sym-test.c
new file mode 100644 (file)
index 0000000..dfc2391
--- /dev/null
@@ -0,0 +1,94 @@
+#include <assert.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "macros.h"
+#include "sym.h"
+
+typedef struct word {
+  sym_base _b;
+  int i;
+} word;
+
+static int cmp(const void *a, const void *b)
+{
+  const word *const *v = b;
+  const word *const *w = a;
+  return (strcmp(SYM_NAME(*w), SYM_NAME(*v)));
+}
+
+int main(void)
+{
+  char buf[256];
+  char *p;
+  sym_table t;
+  size_t n = 0, j;
+
+  sym_create(&t);
+
+  while (fgets(buf, sizeof(buf), stdin)) {
+/*     printf("+++ %s", buf); */
+    buf[strlen(buf) - 1] = 0;
+    p = strtok(buf, " ");
+
+    if (STRCMP(p, ==, "set")) {
+      char *k = strtok(0, " ");
+      int i = atoi(strtok(0, " "));
+      unsigned f;
+      word *w = sym_find(&t, k, -1, sizeof(word), &f);
+      w->i = i;
+      if (!f)
+       n++;
+    } else if (STRCMP(p, ==, "get")) {
+      char *k = strtok(0, " ");
+      word *w = sym_find(&t, k, -1, 0, 0);
+      if (w)
+       printf("%i\n", w->i);
+      else
+       puts("*MISSING*");
+    } else if (STRCMP(p, ==, "del")) {
+      char *k = strtok(0, " ");
+      word *w = sym_find(&t, k, -1, 0, 0);
+      if (w) {
+       sym_remove(&t, w);
+       n--;
+      } else
+       puts("*MISSING*");
+    } else if (STRCMP(p, ==, "count")) {
+      printf("%lu\n", (unsigned long)n);
+    } else if (STRCMP(p, ==, "show")) {
+      sym_iter i;
+      word *w;
+      word **v, **vv;
+
+      if (!n)
+       puts("*EMPTY*");
+      else {
+       v = malloc(n * sizeof(*v));
+       if (!v) {
+         puts("*NOMEM*");
+         continue;
+       }
+       for (vv = v, sym_mkiter(&i, &t), j = 0;
+            (w = sym_next(&i)) != 0;
+            vv++, j++) {
+         assert(j < n);
+         *vv = w;
+       }
+       assert(j == n);
+       qsort(v, n, sizeof(*v), cmp);
+       printf("%s:%i", SYM_NAME(*v), (*v)->i);
+       for (vv = v + 1; --j; vv++)
+         printf(" %s:%i", SYM_NAME(*vv), (*vv)->i);
+       free(v);
+       putchar('\n');
+      }
+    } else
+      puts("*BAD*");
+/*     printf("--- %d\n", n); */
+  }
+
+  sym_destroy(&t);
+  return (0);
+}
diff --git a/struct/tests.at b/struct/tests.at
new file mode 100644 (file)
index 0000000..f54deb5
--- /dev/null
@@ -0,0 +1,67 @@
+### -*-autotest-*-
+###
+### Test script for data structures
+###
+### (c) 2009 Straylight/Edgeware
+###
+
+###----- Licensing notice ---------------------------------------------------
+###
+### This file is part of the mLib utilities library.
+###
+### mLib is free software; you can redistribute it and/or modify
+### it under the terms of the GNU Library General Public License as
+### published by the Free Software Foundation; either version 2 of the
+### License, or (at your option) any later version.
+###
+### mLib is distributed in the hope that it will be useful,
+### but WITHOUT ANY WARRANTY; without even the implied warranty of
+### MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+### GNU Library General Public License for more details.
+###
+### You should have received a copy of the GNU Library General Public
+### License along with mLib; if not, write to the Free
+### Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+### MA 02111-1307, USA.
+
+###--------------------------------------------------------------------------
+### Tests.
+
+## assoc
+AT_SETUP([struct: assoc])
+AT_KEYWORDS([struct assoc])
+AT_SKIP_IF([test "$PYTHON" = :])
+for seed in 0x58677213 0xdfcc2ff4 ""; do
+  $PYTHON SRCDIR/t/sym-gtest.py $seed
+  AT_CHECK([BUILDDIR/t/assoc.t <sym.script], [0], [expout])
+done
+AT_CLEANUP
+
+## darray
+AT_SETUP([struct: darray])
+AT_KEYWORDS([struct darray])
+AT_SKIP_IF([test "$PYTHON" = :])
+for seed in 0x0394946c 0xe8991664 ""; do
+  $PYTHON SRCDIR/t/da-gtest.py $seed
+  AT_CHECK([BUILDDIR/t/darray.t <da.script], [0], [expout])
+done
+AT_CLEANUP
+
+## dstr
+AT_SETUP([struct: dstr-putf])
+AT_KEYWORDS([struct dstr putf dstr_putf])
+AT_CHECK([BUILDDIR/t/dstr-putf.t], [0], [All tests successful.
+])
+AT_CLEANUP
+
+## sym
+AT_SETUP([struct: sym])
+AT_KEYWORDS([struct sym])
+AT_SKIP_IF([test "$PYTHON" = :])
+for seed in 0xdc0f64a3 0xd0b9fad0 ""; do
+  $PYTHON SRCDIR/t/sym-gtest.py $seed
+  AT_CHECK([BUILDDIR/t/sym.t <sym.script], [0], [expout])
+done
+AT_CLEANUP
+
+###----- That's all, folks --------------------------------------------------
diff --git a/sys/Makefile.am b/sys/Makefile.am
new file mode 100644 (file)
index 0000000..f1f48be
--- /dev/null
@@ -0,0 +1,85 @@
+### -*-makefile-*-
+###
+### Build script for system utilities
+###
+### (c) 2009 Straylight/Edgeware
+###
+
+###----- Licensing notice ---------------------------------------------------
+###
+### This file is part of the mLib utilities library.
+###
+### mLib is free software; you can redistribute it and/or modify
+### it under the terms of the GNU Library General Public License as
+### published by the Free Software Foundation; either version 2 of the
+### License, or (at your option) any later version.
+###
+### mLib is distributed in the hope that it will be useful,
+### but WITHOUT ANY WARRANTY; without even the implied warranty of
+### MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+### GNU Library General Public License for more details.
+###
+### You should have received a copy of the GNU Library General Public
+### License along with mLib; if not, write to the Free
+### Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+### MA 02111-1307, USA.
+
+include $(top_srcdir)/vars.am
+
+noinst_LTLIBRARIES      = libsys.la
+libsys_la_SOURCES       =
+
+###--------------------------------------------------------------------------
+### Component files.
+
+## Daemons.
+pkginclude_HEADERS     += daemonize.h
+libsys_la_SOURCES      += daemonize.c
+LIBMANS                        += daemonize.3
+
+## Environment variables.
+pkginclude_HEADERS     += env.h
+libsys_la_SOURCES      += env.c
+LIBMANS                        += env.3
+
+## File and descriptor flags.
+pkginclude_HEADERS     += fdflags.h
+libsys_la_SOURCES      += fdflags.c
+LIBMANS                        += fdflags.3
+
+## File descriptor passing.
+pkginclude_HEADERS     += fdpass.h
+libsys_la_SOURCES      += fdpass.c
+LIBMANS                        += fdpass.3
+
+check_PROGRAMS         += t/fdpass.t
+t_fdpass_t_SOURCES      = t/fdpass-test.c
+t_fdpass_t_CPPFLAGS     = $(TEST_CPPFLAGS)
+t_fdpass_t_LDFLAGS      = -static
+
+## Watching files for modification.
+pkginclude_HEADERS     += fwatch.h
+libsys_la_SOURCES      += fwatch.c
+LIBMANS                        += fwatch.3
+
+## File locking.
+pkginclude_HEADERS     += lock.h
+libsys_la_SOURCES      += lock.c
+LIBMANS                        += lock.3
+
+## File descriptor juggling.
+pkginclude_HEADERS     += mdup.h
+libsys_la_SOURCES      += mdup.c
+LIBMANS                        += mdup.3
+
+check_PROGRAMS         += t/mdup.t
+t_mdup_t_SOURCES        = t/mdup-test.c
+t_mdup_t_CPPFLAGS       = $(TEST_CPPFLAGS)
+t_mdup_t_LDFLAGS        = -static
+
+## Time arithmetic.
+pkginclude_HEADERS     += tv.h
+libsys_la_SOURCES      += tv.c
+LIBMANS                        += tv.3
+
+###----- That's all, folks --------------------------------------------------
diff --git a/sys/daemonize.3 b/sys/daemonize.3
new file mode 100644 (file)
index 0000000..1482bed
--- /dev/null
@@ -0,0 +1,41 @@
+.\" -*-nroff-*-
+.TH daemonize 3 "6 January 2007" "Straylight/Edgeware" "mLib utilities library"
+.SH NAME
+daemonize \- become a background process
+.\" @detachtty
+.\" @daemonize
+.SH SYNOPSIS
+.nf
+.B "#include <mLib/daemonize.h>"
+
+.B "void detachtty(void);"
+.B "int daemonize(void);"
+.fi
+.SH DESCRIPTION
+The
+.B daemonize
+function causes the current process to become a background process.  It
+detaches from its controlling terminal and arranges never to acquire
+another controlling terminal.  If it fails for some reason (probably
+because
+.BR fork (2)
+failed),
+.B daemonize
+returns \-1 and sets
+.BR errno ;
+on success, it returns 0.
+.PP
+The
+.B detachtty
+does half of the job of
+.BR daemonize :
+it detaches from its controlling terminal, and calls
+.BR setsid (2)
+and
+.BR fork (2)
+so that it can't acquire a new controlling terminal in future.  Errors
+are ignored.
+.SH SEE ALSO
+.BR mLib (3).
+.SH AUTHOR
+Mark Wooding, <mdw@distorted.org.uk>
diff --git a/sys/daemonize.c b/sys/daemonize.c
new file mode 100644 (file)
index 0000000..4b1e966
--- /dev/null
@@ -0,0 +1,86 @@
+/* -*-c-*-
+ *
+ * Become a daemon, detaching from terminals
+ *
+ * (c) 2007 Straylight/Edgeware
+ */
+
+/*----- Licensing notice --------------------------------------------------*
+ *
+ * This file is part of the mLib utilities library.
+ *
+ * mLib is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * mLib is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with mLib; if not, write to the Free
+ * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+ * MA 02111-1307, USA.
+ */
+
+/*----- Header files ------------------------------------------------------*/
+
+#include <sys/types.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/ioctl.h>
+
+#include "daemonize.h"
+
+/*----- Main code ---------------------------------------------------------*/
+
+/* --- @detachtty@ --- *
+ *
+ * Arguments:  ---
+ *
+ * Returns:    ---
+ *
+ * Use:                Detaches from the current terminal and ensures it can never
+ *             acquire a new one.  Calls @fork@.
+ */
+
+void detachtty(void)
+{
+#ifdef TIOCNOTTY
+  {
+    int fd;
+    if ((fd = open("/dev/tty", O_RDONLY)) >= 0) {
+      ioctl(fd, TIOCNOTTY);
+      close(fd);
+    }
+  }
+#endif
+  setsid();
+  if (fork() > 0)
+    _exit(0);
+}
+
+/* --- @daemonize@ --- *
+ *
+ * Arguments:  ---
+ *
+ * Returns:    Zero if OK, nonzero on failure.
+ *
+ * Use:                Becomes a daemon.
+ */
+
+int daemonize(void)
+{
+  pid_t kid;
+
+  if ((kid = fork()) < 0)
+    return (-1);
+  if (kid)
+    _exit(0);
+  detachtty();
+  return (0);
+}
+
+/*----- That's all, folks -------------------------------------------------*/
diff --git a/sys/daemonize.h b/sys/daemonize.h
new file mode 100644 (file)
index 0000000..afe0afb
--- /dev/null
@@ -0,0 +1,66 @@
+/* -*-c-*-
+ *
+ * Become a daemon, detaching from terminals
+ *
+ * (c) 2007 Straylight/Edgeware
+ */
+
+/*----- Licensing notice --------------------------------------------------*
+ *
+ * This file is part of the mLib utilities library.
+ *
+ * mLib is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * mLib is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with mLib; if not, write to the Free
+ * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+ * MA 02111-1307, USA.
+ */
+
+#ifndef MLIB_DAEMONIZE_H
+#define MLIB_DAEMONIZE_H
+
+#ifdef __cplusplus
+  extern "C" {
+#endif
+
+/*----- Functions provided ------------------------------------------------*/
+
+/* --- @detachtty@ --- *
+ *
+ * Arguments:  ---
+ *
+ * Returns:    ---
+ *
+ * Use:                Detaches from the current terminal and ensures it can never
+ *             acquire a new one.  Calls @fork@.
+ */
+
+extern void detachtty(void);
+
+/* --- @daemonize@ --- *
+ *
+ * Arguments:  ---
+ *
+ * Returns:    Zero if OK, nonzero on failure.
+ *
+ * Use:                Becomes a daemon.
+ */
+
+extern int daemonize(void);
+
+/*----- That's all, folks -------------------------------------------------*/
+
+#ifdef __cplusplus
+  }
+#endif
+
+#endif
diff --git a/sys/env.3 b/sys/env.3
new file mode 100644 (file)
index 0000000..1818b37
--- /dev/null
+++ b/sys/env.3
@@ -0,0 +1,87 @@
+.\" -*-nroff-*-
+.TH env 3 "26 July 1999" "Straylight/Edgeware" "mLib utilities library"
+.SH "NAME"
+env \- efficient fiddling with environment variables
+.\" @env_get
+.\" @env_put
+.\" @env_import
+.\" @env_export
+.\" @env_destroy
+.SH "SYNOPSIS"
+.nf
+.B "#include <mLib/env.h>"
+
+.BI "char *env_get(sym_table *" t ", const char *" name );
+.BI "void env_put(sym_table *" t ,
+.BI "             const char *" name ", const char *" value );
+.BI "void env_import(sym_table *" t ", char **" env );
+.BI "char **env_export(sym_table *" t );
+.fi
+.SH "DESCRIPTION"
+The functions in the
+.B "<mLib/env.h>"
+header manipulate environment variables stored in a hash table.
+.PP
+The function
+.B env_import
+reads a standard Unix environment array and stores the variables in the
+symbol table.  (A standard Unix environment array is an array of
+pointers to strings of the form
+.IB name = value
+terminated by a null pointer.  This format is used for the global
+.B environ
+variable, and by the
+.BR execve (2)
+function.)  The symbol table must have already been created (see
+.BR sym_create (3)).
+.PP
+The function
+.B env_export
+creates a Unix environment array from a symbol table.  The environment
+array is one big block of memory allocated using
+.BR xmalloc (3);
+hence, one call to
+.BR xfree (3)
+releases all the memory used for the pointer array and the strings.
+.PP
+The
+.B env_get
+function looks up a variable in an environment symbol table.  The
+returned result is the variable's value if it exists, or a null pointer
+if not.
+.PP
+The
+.B env_put
+function sets or deletes environment variables.  If the
+.I name
+argument contains an
+.RB ` = '
+character, it is assumed to be of the form
+.IB n = v\fR;
+the
+.I value
+argument is ignored, and the variable
+.I n
+is assigned the value
+.IR v .
+Otherwise, if
+.I value
+is not a null pointer, the variable
+.I name
+is assigned
+.IR value .
+Finally, if
+.I value
+is null, the variable
+.I name
+is deleted.
+.PP
+The
+.B env_destroy
+function frees an environment symbol table, together with all of the
+environment variables.
+.SH "SEE ALSO"
+.BR sym (3),
+.BR mLib (3).
+.SH "AUTHOR"
+Mark Wooding, <mdw@distorted.org.uk>
diff --git a/sys/env.c b/sys/env.c
new file mode 100644 (file)
index 0000000..1e3d3d1
--- /dev/null
+++ b/sys/env.c
@@ -0,0 +1,206 @@
+/* -*-c-*-
+ *
+ * Fiddling with environment variables
+ *
+ * (c) 1999 Straylight/Edgeware
+ */
+
+/*----- Licensing notice --------------------------------------------------*
+ *
+ * This file is part of the mLib utilities library.
+ *
+ * mLib is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * mLib is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with mLib; if not, write to the Free
+ * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+ * MA 02111-1307, USA.
+ */
+
+/*----- Header files ------------------------------------------------------*/
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "alloc.h"
+#include "sym.h"
+
+/*----- Data structures ---------------------------------------------------*/
+
+typedef struct var {
+  sym_base _base;
+  char *v;
+} var;
+
+/*----- Main code ---------------------------------------------------------*/
+
+/* --- @env_get@ --- *
+ *
+ * Arguments:  @sym_table *t@ = pointer to a symbol table
+ *             @const char *name@ = pointer to variable name to look up
+ *
+ * Returns:    Pointer to corresponding value string, or null.
+ *
+ * Use:                Looks up an environment variable in the table and returns its
+ *             value.  If the variable can't be found, a null pointer is
+ *             returned.
+ */
+
+char *env_get(sym_table *t, const char *name)
+{
+  var *e = sym_find(t, name, -1, 0, 0);
+  return (e ? e->v : 0);
+}
+
+/* --- @env_put@ --- *
+ *
+ * Arguments:  @sym_table *t@ = pointer to a symbol table
+ *             @const char *name@ = pointer to variable name to set
+ *             @const char *value@ = pointer to value string to assign
+ *
+ * Returns:    ---
+ *
+ * Use:                Assigns a value to a variable.  If the @name@ contains an
+ *             equals character, then it's assumed to be of the form
+ *             `VAR=VALUE' and @value@ argument is ignored.  Otherwise, if
+ *             @value@ is null, the variable is deleted.  Finally, the
+ *             normal case: @name@ is a plain name, and @value@ is a normal
+ *             string causes the variable to be assigned the value in the
+ *             way you'd expect.
+ */
+
+void env_put(sym_table *t, const char *name, const char *value)
+{
+  char *q = 0;
+
+  /* --- Sort out the mess with `NAME=VALUE' forms --- */
+
+  {
+    size_t eq = strcspn(name, "=");
+    if (name[eq] == '=') {
+      q = x_alloc(t->t.a, eq + 1);
+      memcpy(q, name, eq);
+      q[eq] = 0;
+      value = name + eq + 1;
+      name = q;
+    }
+  }
+
+  /* --- Read the current value --- */
+
+  if (!value) {
+    var *v;
+    if ((v = sym_find(t, name, -1, 0, 0)) != 0) {
+      x_free(t->t.a, v->v);
+      sym_remove(t, v);
+    }
+  } else {
+    unsigned found;
+    var *v = sym_find(t, name, -1, sizeof(*v), &found);
+    if (found)
+      x_free(t->t.a, v->v);
+    v->v = x_strdup(t->t.a, value);
+  }
+
+  /* --- Tidying --- */
+
+  if (q)
+    xfree(q);
+}
+
+/* --- @env_import@ --- *
+ *
+ * Arguments:  @sym_table *t@ = pointer to a symbol table
+ *             @char **env@ = pointer to an environment list
+ *
+ * Returns:    ---
+ *
+ * Use:                Inserts all of the environment variables listed into a symbol
+ *             table for rapid access.  Equivalent to a lot of calls to
+ *             @env_put@.
+ */
+
+void env_import(sym_table *t, char **env)
+{
+  while (*env) {
+    env_put(t, *env, 0);
+    env++;
+  }
+}
+
+/* --- @env_export@ --- *
+ *
+ * Arguments:  @sym_table *t@ = pointer to a symbol table
+ *
+ * Returns:    A big environment list.
+ *
+ * Use:                Extracts an environment table from a symbol table
+ *             representation of an environment.  The table and all of the
+ *             strings are in one big block allocated from the heap.
+ */
+
+char **env_export(sym_table *t)
+{
+  size_t n = 1;
+  size_t sz = 0;
+  sym_iter i;
+  var *v;
+  char **env;
+  char *p, **pp;
+
+  /* --- Work out sizes for everything --- */
+
+  for (sym_mkiter(&i, t); (v = sym_next(&i)) != 0; ) {
+    n++;
+    sz += SYM_LEN(v) + strlen(v->v) + 2;
+  }
+
+  /* --- Allocate the big chunk of memory --- */
+
+  env = pp = xmalloc(n * sizeof(char *) + sz);
+  p = (char *)(env + n);
+
+  /* --- Dump the output in the big chunk of memory --- */
+
+  for (sym_mkiter(&i, t); (v = sym_next(&i)) != 0; ) {
+    const char *name = SYM_NAME(v);
+    size_t nlen = strlen(name), vlen = strlen(v->v);
+    *pp++ = p;
+    memcpy(p, name, nlen); p += nlen;
+    *p++ = '=';
+    memcpy(p, v->v, vlen); p += vlen;
+    *p++ = 0;
+  }
+  *pp++ = 0;
+  return (env);
+}
+
+/* --- @env_destroy@ --- *
+ *
+ * Arguments:  @sym_table *t@ = pointer to symbol table
+ *
+ * Returns:    ---
+ *
+ * Use:                Destroys all the variables in the symbol table.
+ */
+
+void env_destroy(sym_table *t)
+{
+  sym_iter i;
+  var *v;
+
+  for (sym_mkiter(&i, t); (v = sym_next(&i)) != 0; )
+    x_free(t->t.a, v->v);
+  sym_destroy(t);
+}
+
+/*----- That's all, folks -------------------------------------------------*/
diff --git a/sys/env.h b/sys/env.h
new file mode 100644 (file)
index 0000000..6001242
--- /dev/null
+++ b/sys/env.h
@@ -0,0 +1,121 @@
+/* -*-c-*-
+ *
+ * Fiddling with environment variables
+ *
+ * (c) 1999 Straylight/Edgeware
+ */
+
+/*----- Licensing notice --------------------------------------------------*
+ *
+ * This file is part of the mLib utilities library.
+ *
+ * mLib is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * mLib is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with mLib; if not, write to the Free
+ * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+ * MA 02111-1307, USA.
+ */
+
+#ifndef MLIB_ENV_H
+#define MLIB_ENV_H
+
+#ifdef __cplusplus
+  extern "C" {
+#endif
+
+/*----- Header files ------------------------------------------------------*/
+
+#ifndef MLIB_SYM_H
+#  include "sym.h"
+#endif
+
+/*----- Functions provided ------------------------------------------------*/
+
+/* --- @env_get@ --- *
+ *
+ * Arguments:  @sym_table *t@ = pointer to a symbol table
+ *             @const char *name@ = pointer to variable name to look up
+ *
+ * Returns:    Pointer to corresponding value string, or null.
+ *
+ * Use:                Looks up an environment variable in the table and returns its
+ *             value.  If the variable can't be found, a null pointer is
+ *             returned.
+ */
+
+extern char *env_get(sym_table */*t*/, const char */*name*/);
+
+/* --- @env_put@ --- *
+ *
+ * Arguments:  @sym_table *t@ = pointer to a symbol table
+ *             @const char *name@ = pointer to variable name to set
+ *             @const char *value@ = pointer to value string to assign
+ *
+ * Returns:    ---
+ *
+ * Use:                Assigns a value to a variable.  If the @name@ contains an
+ *             equals character, then it's assumed to be of the form
+ *             `VAR=VALUE' and @value@ argument is ignored.  Otherwise, if
+ *             @value@ is null, the variable is deleted.  Finally, the
+ *             normal case: @name@ is a plain name, and @value@ is a normal
+ *             string causes the variable to be assigned the value in the
+ *             way you'd expect.
+ */
+
+extern void env_put(sym_table */*t*/,
+                   const char */*name*/, const char */*value*/);
+
+/* --- @env_import@ --- *
+ *
+ * Arguments:  @sym_table *t@ = pointer to a symbol table
+ *             @char **env@ = pointer to an environment list
+ *
+ * Returns:    ---
+ *
+ * Use:                Inserts all of the environment variables listed into a symbol
+ *             table for rapid access.  Equivalent to a lot of calls to
+ *             @env_put@.
+ */
+
+extern void env_import(sym_table */*t*/, char **/*env*/);
+
+/* --- @env_export@ --- *
+ *
+ * Arguments:  @sym_table *t@ = pointer to a symbol table
+ *
+ * Returns:    A big environment list.
+ *
+ * Use:                Extracts an environment table from a symbol table
+ *             representation of an environment.  The table and all of the
+ *             strings are in one big block allocated from the heap.
+ */
+
+extern char **env_export(sym_table */*t*/);
+
+/* --- @env_destroy@ --- *
+ *
+ * Arguments:  @sym_table *t@ = pointer to symbol table
+ *
+ * Returns:    ---
+ *
+ * Use:                Destroys all the variables in the symbol table.
+ */
+
+extern void env_destroy(sym_table */*t*/);
+
+/*----- That's all, folks -------------------------------------------------*/
+
+#ifdef __cplusplus
+  }
+#endif
+
+#endif
diff --git a/sys/fdflags.3 b/sys/fdflags.3
new file mode 100644 (file)
index 0000000..81f3d83
--- /dev/null
@@ -0,0 +1,82 @@
+.\" -*-nroff-*-
+.de VS
+.sp 1
+.RS
+.nf
+.ft B
+..
+.de VE
+.ft R
+.fi
+.RE
+.sp 1
+..
+.TH fdflags 3 "23 July 1999" "Straylight/Edgeware" "mLib utilities library"
+.SH "NAME"
+fdflags \- set file and file descriptor flags
+.\" @fdflags
+.SH "SYNOPSIS"
+.nf
+.B "#include <mLib/fdflags.h>"
+
+.BI "int fdflags(int " fd ,
+.BI "            unsigned " fbic ", unsigned " fxor ,
+.BI "            unsigned " fdbic ", unsigned " fdxor );
+.fi
+.SH "DESCRIPTION"
+.B fdflags
+is a convenience function for setting file and file descriptor flags
+using
+.BR fcntl (2).
+.PP
+The file flags are read using
+.BR F_GETFL ,
+the new flags are calculated as
+.sp 1
+.RS
+.I new-flags
+=
+.BI ( old-flags
+.B &
+.BI ~ fbic )
+.B ^
+.I fxor
+.RE
+.sp 1
+and the result written back using
+.BR F_SETFL .
+.PP
+Similarly the file descriptor flags are read using
+.BR F_GETFD ,
+the new flags calculated as
+.sp 1
+.RS
+.I new-flags
+=
+.BI ( old-flags
+.B &
+.BI ~ fdbic )
+.B ^
+.I fdxor
+.RE
+.sp 1
+and the result written back using
+.BR F_SETFD .
+.PP
+If all went well,
+.B fdflags
+returns zero; if there was an error, \-1 is returned.
+.SH "EXAMPLES"
+To set the non-blocking and close-on-exec flags:
+.VS
+fdflags(fd, O_NONBLOCK, O_NONBLOCK, FD_CLOEXEC, FD_CLOEXEC);
+.VE
+To clear the non-blocking and close-on-exec flags:
+.VS
+fdflags(fd, O_NONBLOCK, 0, FD_CLOEXEC, 0);
+.VE
+.sp -1
+.SH "SEE ALSO"
+.BR mLib (3).
+.SH "AUTHOR"
+Mark Wooding, <mdw@distorted.org.uk>
diff --git a/sys/fdflags.c b/sys/fdflags.c
new file mode 100644 (file)
index 0000000..02469ad
--- /dev/null
@@ -0,0 +1,74 @@
+/* -*-c-*-
+ *
+ * Manipulates flags on file descriptors
+ *
+ * (c) 1999 Straylight/Edgeware
+ */
+
+/*----- Licensing notice --------------------------------------------------*
+ *
+ * This file is part of the mLib utilities library.
+ *
+ * mLib is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * mLib is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with mLib; if not, write to the Free
+ * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+ * MA 02111-1307, USA.
+ */
+
+/*----- Header files ------------------------------------------------------*/
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <fcntl.h>
+
+#include "fdflags.h"
+
+/*----- Main code ---------------------------------------------------------*/
+
+/* --- @fdflags@ --- *
+ *
+ * Arguments:  @int fd@ = file descriptor to fiddle with
+ *             @unsigned fbic, fxor@ = file flags to set and clear
+ *             @unsigned fdbic, fdxor@ = descriptor flags to set and clear
+ *
+ * Returns:    Zero if successful, @-1@ if not.
+ *
+ * Use:                Sets file descriptor flags in what is, I hope, an obvious
+ *             way.
+ */
+
+int fdflags(int fd, unsigned fbic, unsigned fxor,
+           unsigned fdbic, unsigned fdxor)
+{
+  int f, ff;
+
+  if (fbic || fxor) {
+    if ((f = fcntl(fd, F_GETFL)) == -1)
+      return (-1);
+    ff = (f & ~fbic) ^ fxor;
+    if (f != ff && fcntl(fd, F_SETFL, ff) == -1)
+      return (-1);
+  }
+  if (fdbic || fdxor) {
+    if ((f = fcntl(fd, F_GETFD)) == -1)
+      return (-1);
+    ff = (f & ~fdbic) ^ fdxor;
+    if (f != ff && fcntl(fd, F_SETFD, ff) == -1)
+      return (-1);
+  }
+  return (0);
+}
+
+/*----- That's all, folks -------------------------------------------------*/
diff --git a/sys/fdflags.h b/sys/fdflags.h
new file mode 100644 (file)
index 0000000..bccc433
--- /dev/null
@@ -0,0 +1,67 @@
+/* -*-c-*-
+ *
+ * Manipulates flags on file descriptors
+ *
+ * (c) 1999 Straylight/Edgeware
+ */
+
+/*----- Licensing notice --------------------------------------------------*
+ *
+ * This file is part of the mLib utilities library.
+ *
+ * mLib is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * mLib is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with mLib; if not, write to the Free
+ * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+ * MA 02111-1307, USA.
+ */
+
+#ifndef MLIB_FDFLAGS_H
+#define MLIB_FDFLAGS_H
+
+#ifdef __cplusplus
+  extern "C" {
+#endif
+
+/*----- Header files ------------------------------------------------------*/
+
+#include <fcntl.h>
+
+/*----- Magic numbers -----------------------------------------------------*/
+
+#ifndef FD_CLOEXEC
+#  define FD_CLOEXEC 1
+#endif
+
+/*----- Functions provided ------------------------------------------------*/
+
+/* --- @fdflags@ --- *
+ *
+ * Arguments:  @int fd@ = file descriptor to fiddle with
+ *             @unsigned fbic, fxor@ = file flags to set and clear
+ *             @unsigned fdbic, fdxor@ = descriptor flags to set and clear
+ *
+ * Returns:    Zero if successful.
+ *
+ * Use:                Sets file descriptor flags.
+ */
+
+int fdflags(int /*fd*/, unsigned /*fbic*/, unsigned /*fxor*/,
+           unsigned /*fdbic*/, unsigned /*fdxor*/);
+
+/*----- That's all, folks -------------------------------------------------*/
+
+#ifdef __cplusplus
+  }
+#endif
+
+#endif
diff --git a/sys/fdpass.3 b/sys/fdpass.3
new file mode 100644 (file)
index 0000000..b643bde
--- /dev/null
@@ -0,0 +1,54 @@
+.\" -*-nroff-*-
+.TH fdpass 3 "28 November 2003" "Straylight/Edgeware" "mLib utilities library"
+.SH NAME
+fdpass \- file descriptor passing
+.\" @fdpass_send
+.\" @fdpass_recv
+.SH SYNOPSIS
+.nf
+.B "#include <mLib/fdpass.h>"
+
+.BI "ssize_t fdpass_send(int " sock ", int " fd ", const void *" p ", size_t " sz );
+.BI "ssize_t fdpass_recv(int " sock ", int *" fd ", void *" p ", size_t " sz );
+.fi
+.SH DESCRIPTION
+The function
+.B fdpass_send
+sends the file descriptor
+.I fd
+as ancillary data attached to the buffer pointed to by
+.I p
+of length
+.I sz
+over the Unix-domain socket
+.IR sock .
+It returns the amount of data sent, or \-1 on error.  For more details,
+see
+.BR sendmsg (2)
+and
+.BR unix (7).
+.PP
+The function
+.B fdpass_recv
+receives at most
+.I sz
+bytes of data into the buffer pointed to by
+.IR p ,
+together with at most one file descriptor passed as ancillary data,
+which is written to the integer pointed to by
+.IR fd .
+Other file descriptors received are closed; any other ancillary messages
+are ignored.  If no file descriptor is received,
+.BI * fd
+is set to \-1.  The function returns the number of bytes read, or \-1 on
+error.  For more details, see
+.BR recvmsg (2)
+and
+.BR unix (7).
+.SH "SEE ALSO"
+.BR recvmsg (2),
+.BR sendmsg (2),
+.BR mLib (3),
+.BR unix (7).
+.SH AUTHOR
+Mark Wooding, <mdw@distorted.org.uk>
diff --git a/sys/fdpass.c b/sys/fdpass.c
new file mode 100644 (file)
index 0000000..15a4a2c
--- /dev/null
@@ -0,0 +1,162 @@
+/* -*-c-*-
+ *
+ * File descriptor passing
+ *
+ * (c) 2003 Straylight/Edgeware
+ */
+
+/*----- Licensing notice --------------------------------------------------*
+ *
+ * This file is part of the mLib utilities library.
+ *
+ * mLib is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * mLib is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with mLib; if not, write to the Free
+ * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+ * MA 02111-1307, USA.
+ */
+
+/*----- Header files ------------------------------------------------------*/
+
+#include "config.h"
+
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <sys/types.h>
+#include <sys/time.h>
+#include <unistd.h>
+#include <sys/socket.h>
+#include <sys/uio.h>
+#include <sys/un.h>
+
+#include "fdpass.h"
+
+/*----- Main code ---------------------------------------------------------*/
+
+/* --- @fdpass_send@ --- *
+ *
+ * Arguments:  @int sock@ = socket to send over
+ *             @int fd@ = file descriptor to send
+ *             @const void *p@ = pointer to data to send
+ *             @size_t sz@ = size of buffer to send
+ *
+ * Returns:    On error, @-1@, otherwise number of bytes transferred from
+ *             @p@.
+ *
+ * Use:                Sends a copy of file descriptor @fd@ to the other end of
+ *             @sock@.
+ */
+
+ssize_t fdpass_send(int sock, int fd, const void *p, size_t sz)
+{
+  struct iovec iov;
+  struct msghdr msg;
+#ifndef HAVE_MSG_ACCRIGHTS
+  char buf[CMSG_SPACE(sizeof(fd))];
+  struct cmsghdr *cmsg;
+#endif
+
+  iov.iov_base = (/*unconst*/ void *)p;
+  iov.iov_len = sz;
+  msg.msg_name = 0;
+  msg.msg_namelen = 0;
+  msg.msg_iov = &iov;
+  msg.msg_iovlen = 1;
+#ifdef HAVE_MSG_ACCRIGHTS
+  msg.msg_accrights = &fd;
+  msg.msg_accrightslen = sizeof(fd);
+#else
+  msg.msg_flags = 0;
+  msg.msg_control = buf;
+  msg.msg_controllen = sizeof(buf);
+  cmsg = CMSG_FIRSTHDR(&msg);
+  cmsg->cmsg_len = CMSG_LEN(sizeof(fd));
+  cmsg->cmsg_level = SOL_SOCKET;
+  cmsg->cmsg_type = SCM_RIGHTS;
+  memcpy(CMSG_DATA(cmsg), &fd, sizeof(fd));
+#endif
+  return (sendmsg(sock, &msg, 0));
+}
+
+/* --- @fdpass_recv@ --- *
+ *
+ * Arguments:  @int sock@ = socket to send over
+ *             @int *fd@ = where to put received descriptor
+ *             @void *p@ = pointer to where to put data
+ *             @size_t sz@ = size of buffer
+ *
+ * Returns:    On error, @-1@, otherwise number of bytes transferred.
+ *
+ * Use:                Receives a file descriptor.  If the call succeeds, and there
+ *             was a file descriptor, then @fd@ won't be @-1@ on exit;
+ *             otherwise it will.  At most one descriptor will be collected.
+ */
+
+/* Qemu 2.8.1 clobbers 12 bytes beyond the end of the control-message
+ * buffer.  This is fixed in 2.12, but I'll bodge it for the sake of Debian
+ * stable.
+ */
+#define QEMU_SCRATCHSZ 16
+
+ssize_t fdpass_recv(int sock, int *fd, void *p, size_t sz)
+{
+  struct iovec iov;
+  struct msghdr msg;
+  ssize_t rc;
+#ifndef HAVE_MSG_ACCRIGHTS
+  char buf[CMSG_SPACE(sizeof(fd)) + QEMU_SCRATCHSZ];
+  struct cmsghdr *cmsg;
+  int fdtmp;
+#endif
+
+  *fd = -1;
+  iov.iov_base = p;
+  iov.iov_len = sz;
+  msg.msg_name = 0;
+  msg.msg_namelen = 0;
+  msg.msg_iov = &iov;
+  msg.msg_iovlen = 1;
+#ifdef HAVE_MSG_ACCRIGHTS
+  msg.msg_accrights = fd;
+  msg.msg_accrightslen = sizeof(*fd);
+#else
+  msg.msg_flags = 0;
+  msg.msg_control = buf;
+  msg.msg_controllen = sizeof(buf) - QEMU_SCRATCHSZ;
+#endif
+  if ((rc = recvmsg(sock, &msg, 0)) < 0)
+    return (rc);
+#ifdef HAVE_MSG_ACCRIGHTS
+  if (msg.msg_accrightslen < sizeof(*fd))
+    *fd = -1;
+#else
+  for (cmsg = CMSG_FIRSTHDR(&msg); cmsg; cmsg = CMSG_NXTHDR(&msg, cmsg)) {
+    if (cmsg->cmsg_level == SOL_SOCKET &&
+       cmsg->cmsg_type == SCM_RIGHTS &&
+       cmsg->cmsg_len >= CMSG_LEN(sizeof(*fd))) {
+      memcpy(&fdtmp, CMSG_DATA(cmsg), sizeof(fdtmp));
+      if (*fd == -1)
+       *fd = fdtmp;
+      else
+       close(fdtmp);
+    }
+  }
+#endif
+  return (rc);
+}
+
+#undef QEMU_SCRATCHSZ
+
+/*----- That's all, folks -------------------------------------------------*/
diff --git a/sys/fdpass.h b/sys/fdpass.h
new file mode 100644 (file)
index 0000000..3fb8f74
--- /dev/null
@@ -0,0 +1,82 @@
+/* -*-c-*-
+ *
+ * File descriptor passing
+ *
+ * (c) 2003 Straylight/Edgeware
+ */
+
+/*----- Licensing notice --------------------------------------------------*
+ *
+ * This file is part of the mLib utilities library.
+ *
+ * mLib is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * mLib is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with mLib; if not, write to the Free
+ * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+ * MA 02111-1307, USA.
+ */
+
+#ifndef MLIB_FDPASS_H
+#define MLIB_FDPASS_H
+
+#ifdef __cplusplus
+  extern "C" {
+#endif
+
+/*----- Header files ------------------------------------------------------*/
+
+#include <sys/types.h>
+#include <unistd.h>
+
+/*----- Functions provided ------------------------------------------------*/
+
+/* --- @fdpass_send@ --- *
+ *
+ * Arguments:  @int sock@ = socket to send over
+ *             @int fd@ = file descriptor to send
+ *             @const void *p@ = pointer to data to send
+ *             @size_t sz@ = size of buffer to send
+ *
+ * Returns:    On error, @-1@, otherwise number of bytes transferred from
+ *             @p@.
+ *
+ * Use:                Sends a copy of file descriptor @fd@ to the other end of
+ *             @sock@.
+ */
+
+extern ssize_t fdpass_send(int /*sock*/, int /*fd*/,
+                          const void */*p*/, size_t /*sz*/);
+
+/* --- @fdpass_recv@ --- *
+ *
+ * Arguments:  @int sock@ = socket to send over
+ *             @int *fd@ = where to put received descriptor
+ *             @void *p@ = pointer to where to put data
+ *             @size_t sz@ = size of buffer
+ *
+ * Returns:    On error, @-1@, otherwise number of bytes transferred.
+ *
+ * Use:                Receives a file descriptor.  If the call succeeds, and there
+ *             was a file descriptor, then @fd@ won't be @-1@ on exit;
+ *             otherwise it will.  At most one descriptor will be collected.
+ */
+
+extern ssize_t fdpass_recv(int /*sock*/, int */*fd*/,
+                          void */*p*/, size_t /*sz*/);
+
+/*----- That's all, folks -------------------------------------------------*/
+
+#ifdef __cplusplus
+  }
+#endif
+
+#endif
diff --git a/sys/fwatch.3 b/sys/fwatch.3
new file mode 100644 (file)
index 0000000..c2329ea
--- /dev/null
@@ -0,0 +1,41 @@
+.\" -*-nroff-*-
+.TH fwatch 3 "3 February 2001" "Straylight/Edgeware" "mLib utilities library"
+.SH NAME
+fwatch \- watch a file for changes
+.\" @fwatch_init
+.\" @fwatch_initfd
+.\" @fwatch_update
+.\" @fwatch_updatefd
+.SH SYNOPSIS
+.nf
+.B "#include <mLib/fwatch.h>"
+
+.BI "void fwatch_init(fwatch *" f ", const char *" name );
+.BI "void fwatch_initfd(fwatch *" f ", int " fd );
+.BI "int fwatch_update(fwatch *" f ", const char *" name );
+.BI "int fwatch_updatefd(fwatch *" f ", int " fd );
+.fi
+.SH DESCRIPTION
+These functions watch a file for changes.  Use
+.B fwatch_init
+or
+.B fwatch_initfd
+to initialize a
+.B fwatch
+block with information about a file; then later, the functions
+.B fwatch_update
+and
+.B fwatch_updatefd
+will update the information in the structure and inform you whether the
+file changed.
+.PP
+The
+.B fwatch
+functions can't return errors: they remember errors as part of the file
+state instead.  The
+.B update
+functions return zero if the file has not changed or nonzero if it has.
+.SH SEE ALSO
+.BR mLib (3).
+.SH AUTHOR
+Mark Wooding, <mdw@distorted.org.uk>
diff --git a/sys/fwatch.c b/sys/fwatch.c
new file mode 100644 (file)
index 0000000..69f5498
--- /dev/null
@@ -0,0 +1,131 @@
+/* -*-c-*-
+ *
+ * Watch a file for changes
+ *
+ * (c) 2001 Straylight/Edgeware
+ */
+
+/*----- Licensing notice --------------------------------------------------*
+ *
+ * This file is part of the mLib utilities library.
+ *
+ * mLib is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * mLib is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with mLib; if not, write to the Free
+ * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+ * MA 02111-1307, USA.
+ */
+
+/*----- Header files ------------------------------------------------------*/
+
+#include <errno.h>
+#include <string.h>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include "fwatch.h"
+
+/*----- Main code ---------------------------------------------------------*/
+
+/* --- @doupdate@ --- *
+ *
+ * Arguments:  @fwatch *f@ = pointer to an @fwatch@ structure
+ *             @struct stat *st@ = pointer to file state
+ *             @int err@ = whether there was an error
+ *
+ * Returns:    Nonzero if the file has changed.
+ *
+ * Use:                Updates the information in an @fwatch@ block.
+ */
+
+static void copy(fwatch *f, struct stat *st)
+{
+  f->dev = st->st_dev; f->ino = st->st_ino;
+  f->mtime = st->st_mtime; f->size = st->st_size;
+  f->mode = st->st_mode;
+  f->uid = st->st_uid; f->gid = st->st_gid;
+}
+
+static int doupdate(fwatch *f, struct stat *st, int err)
+{
+  /* --- Fiddle with the error state --- *
+   *
+   * Only the presence or absence of error is interesting to someone wanting
+   * to read the file.
+   */
+
+  if (err)
+    err = errno;
+  if (err || f->err) {
+    if (!err != !f->err) {
+      f->err = err;
+      if (!err)
+       copy(f, st);
+      return (1);
+    }
+    return (0);
+  }
+
+  /* --- Check everything else --- */
+
+  if (st->st_dev != f->dev || st->st_ino != f->ino ||
+      st->st_mtime != f->mtime || st->st_size != f->size ||
+      st->st_mode != f->mode ||
+      st->st_uid != f->uid || st->st_gid != f->gid) {
+    copy(f, st);
+    return (1);
+  }
+  return (0);
+}
+
+/* --- @fwatch_init@, @fwatch_update@, etc --- *
+ *
+ * Arguments:  @fwatch *f@ = pointer to an @fwatch@ structure
+ *             @const char *name@ = name of the file to watch
+ *             @int fd@ = a file descriptor to watch
+ *
+ * Returns:    The @update@ functions return nonzero if the file has
+ *             changed.
+ *
+ * Use:                Stores information about a file which can be used to watch
+ *             for changes.  The structures may be freed without telling
+ *             anyone.
+ */
+
+void fwatch_init(fwatch *f, const char *name)
+{
+  struct stat st;
+  memset(f, 0, sizeof(*f));
+  doupdate(f, &st, stat(name, &st));
+}
+
+void fwatch_initfd(fwatch *f, int fd)
+{
+  struct stat st;
+  memset(f, 0, sizeof(*f));
+  doupdate(f, &st, fstat(fd, &st));
+}
+
+int fwatch_update(fwatch *f, const char *name)
+{
+  struct stat st;
+  return (doupdate(f, &st, stat(name, &st)));
+}
+
+int fwatch_updatefd(fwatch *f, int fd)
+{
+  struct stat st;
+  return (doupdate(f, &st, fstat(fd, &st)));
+}
+
+/*----- That's all, folks -------------------------------------------------*/
diff --git a/sys/fwatch.h b/sys/fwatch.h
new file mode 100644 (file)
index 0000000..f05532c
--- /dev/null
@@ -0,0 +1,80 @@
+/* -*-c-*-
+ *
+ * Watch a file for changes
+ *
+ * (c) 2001 Straylight/Edgeware
+ */
+
+/*----- Licensing notice --------------------------------------------------*
+ *
+ * This file is part of the mLib utilities library.
+ *
+ * mLib is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * mLib is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with mLib; if not, write to the Free
+ * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+ * MA 02111-1307, USA.
+ */
+
+#ifndef MLIB_FWATCH_H
+#define MLIB_FWATCH_H
+
+#ifdef __cplusplus
+  extern "C" {
+#endif
+
+/*----- Header files ------------------------------------------------------*/
+
+#include <sys/types.h>
+#include <sys/stat.h>
+
+/*----- Data structures ---------------------------------------------------*/
+
+typedef struct fwatch {
+  int err;
+  dev_t dev;
+  ino_t ino;
+  time_t mtime;
+  off_t size;
+  mode_t mode;
+  uid_t uid;
+  gid_t gid;
+} fwatch;
+
+/*----- Functions provided ------------------------------------------------*/
+
+/* --- @fwatch_init@, @fwatch_update@, etc --- *
+ *
+ * Arguments:  @fwatch *f@ = pointer to an @fwatch@ structure
+ *             @const char *name@ = name of the file to watch
+ *             @int fd@ = a file descriptor to watch
+ *
+ * Returns:    The @update@ functions return nonzero if the file has
+ *             changed.
+ *
+ * Use:                Stores information about a file which can be used to watch
+ *             for changes.  The structures may be freed without telling
+ *             anyone.
+ */
+
+extern void fwatch_init(fwatch */*f*/, const char */*name*/);
+extern void fwatch_initfd(fwatch */*f*/, int /*fd*/);
+extern int fwatch_update(fwatch */*f*/, const char */*name*/);
+extern int fwatch_updatefd(fwatch */*f*/, int /*fd*/);
+
+/*----- That's all, folks -------------------------------------------------*/
+
+#ifdef __cplusplus
+  }
+#endif
+
+#endif
diff --git a/sys/lock.3 b/sys/lock.3
new file mode 100644 (file)
index 0000000..75460b4
--- /dev/null
@@ -0,0 +1,61 @@
+.\" -*-nroff-*-
+.TH lock 3 "23 May 1999" "Straylight/Edgeware" "mLib utilities library"
+.SH NAME
+lock \- oversimplified file locking interface
+.\" @lock_file
+.SH SYNOPSIS
+.nf
+.B "#include <mLib/lock.h>"
+
+.BI "int lock_file(int " fd ", unsigned " how );
+.fi
+.SH DESCRIPTION
+The
+.B lock_file
+function provides an extremely simplistic interface to POSIX
+.BR fcntl (2)
+locking.  It locks only entire files, not sections of files.  It doesn't
+have a nonblocking `is this file locked?' function.
+.PP
+On entry,
+.I fd
+should be a file descriptor on an open file, and
+.I how
+is a constant which describes how the file is to be locked.  The
+possible values of
+.I how
+are:
+.TP
+.B LOCK_EXCL
+Lock the file exclusively.  Attempts to lock the file exclusively or
+nonexclusively will fail until the file is unlocked.
+.TP
+.B LOCK_NONEXCL
+Lock the file nonexclusively.  Until the file is unlocked, attempts to
+lock it exclusively will fail, but other nonexclusive locks will
+succeed.
+.TP
+.B LOCK_UNLOCK
+Unlocks a locked file.  Any locks afterwards can succeed.
+.PP
+The
+.B lock_file
+function will block if it can't obtain a lock immediately.  It will time
+itself out after a short while (10 seconds in the current
+implementation) if the lock doesn't become available.
+.PP
+If the call succeeds,
+.B lock_file
+returns zero.  On failure, \-1 is returned, and
+.B errno
+is set to an appropriate value.  Most of the error returns are from
+.BR fcntl (2)
+(q.v.).  If the lock operation times out,
+.B errno
+is set to
+.BR EINTR .
+.SH "SEE ALSO"
+.BR fcntl (2),
+.BR mLib (3).
+.SH AUTHOR
+Mark Wooding, <mdw@distorted.org.uk>
diff --git a/sys/lock.c b/sys/lock.c
new file mode 100644 (file)
index 0000000..02d5d7a
--- /dev/null
@@ -0,0 +1,173 @@
+/* -*-c-*-
+ *
+ * Simplified POSIX locking interface
+ *
+ * (c) 1997 Straylight/Edgeware
+ */
+
+/*----- Licensing notice --------------------------------------------------*
+ *
+ * This file is part of the mLib utilities library.
+ *
+ * mLib is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * mLib is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with mLib; if not, write to the Free
+ * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+ * MA 02111-1307, USA.
+ */
+
+/*----- Header files ------------------------------------------------------*/
+
+#include <errno.h>
+#include <setjmp.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+
+#include <sys/types.h>
+#include <unistd.h>
+#include <fcntl.h>
+
+#include "lock.h"
+
+/*----- Tunable constants -------------------------------------------------*/
+
+#define LOCK_TIMEOUT 10                        /* Maximum time in seconds to wait */
+
+/*----- Static variables --------------------------------------------------*/
+
+static jmp_buf jmp;                    /* Jump here to interrup @fcntl@ */
+
+/*----- Main code ---------------------------------------------------------*/
+
+/* --- @sigalrm@ --- *
+ *
+ * Arguments:  @int sig@ = signal number
+ *
+ * Returns:    ---
+ *
+ * Use:                Makes sure that a @SIGALRM@ signal interrupts system calls.
+ */
+
+static void sigalrm(int sig) { longjmp(jmp, 1); }
+
+/* --- @lock_file@ --- *
+ *
+ * Arguments:  @int fd@ = file descriptor to lock
+ *             @unsigned how@ = type of lock required
+ *
+ * Returns:    0 if OK, -1 if it failed.
+ *
+ * Use:                Acquires a lock on the given file.  The value @how@
+ *             specifies the type of lock to acquire: @LOCK_EXCL@ gets
+ *             an exclusive (write) lock; @LOCK_NONEXCL@ gets a non-
+ *             exclusive (read) lock and @LOCK_UNLOCK@ releases any locks.
+ *             Acquiring a lock gets timed out after a while with an
+ *             error.
+ */
+
+int lock_file(int fd, unsigned how)
+{
+  struct flock fk;
+  struct sigaction sa, osa;
+  sigset_t ss, oss;
+  int e;
+  int al, d, left;
+
+  /* --- Fill in the easy bits --- */
+
+  fk.l_whence = SEEK_SET;
+  fk.l_start = 0;
+  fk.l_len = 0;
+
+  /* --- Unlocking is really easy --- */
+
+  if (how == LOCK_UNLOCK) {
+    fk.l_type = F_UNLCK;
+    return (fcntl(fd, F_SETLK, &fk));
+  }
+
+  /* --- Decide how to do the locking --- */
+
+  if (how == LOCK_EXCL)
+    fk.l_type = F_WRLCK;
+  else if (how == LOCK_NONEXCL)
+    fk.l_type = F_RDLCK;
+  else {
+    errno = EINVAL;
+    return (-1);
+  }
+
+  /* --- Block @SIGALRM@ for a while --- *
+   *
+   * I don't want stray alarms going off while I'm busy here.
+   */
+
+  sigemptyset(&ss);
+  sigaddset(&ss, SIGALRM);
+  if (sigprocmask(SIG_BLOCK, &ss, &oss))
+    return (-1);
+
+  /* --- Set up the signal handler --- */
+
+  sa.sa_handler = sigalrm;
+  sa.sa_flags = 0;
+#ifdef SA_INTERRUPT
+  sa.sa_flags |= SA_INTERRUPT;
+#endif
+  sigemptyset(&sa.sa_mask);
+  if (sigaction(SIGALRM, &sa, &osa))
+    return (-1);
+
+  /* --- Set up the alarm, remembering when it's meant to go off --- */
+
+  al = alarm(0);
+  if (al && al < LOCK_TIMEOUT)
+    d = al;
+  else
+    d = LOCK_TIMEOUT;
+  alarm(d);
+
+  /* --- Set up the return context for the signal handler --- */
+
+  if (setjmp(jmp)) {
+    sigprocmask(SIG_SETMASK, &oss, 0);
+    errno = EINTR;
+    e = -1;
+    goto done;
+  }
+
+  /* --- Unblock the signal and we're ready --- */
+
+  if (sigprocmask(SIG_SETMASK, &oss, 0)) {
+    alarm(al);
+    e = -1;
+    goto done;
+  }
+
+  /* --- Do it --- */
+
+  e = fcntl(fd, F_SETLKW, &fk);
+
+  /* --- Tidy up the mess I left --- */
+
+  left = alarm(0);
+  if (al)
+    alarm(al - d + left);
+done:
+  sigaction(SIGALRM, &osa, 0);
+  return (e);
+}
+
+/*----- That's all, fokls -------------------------------------------------*/
diff --git a/sys/lock.h b/sys/lock.h
new file mode 100644 (file)
index 0000000..83c5b27
--- /dev/null
@@ -0,0 +1,68 @@
+/* -*-c-*-
+ *
+ * Simplified POSIX locking interface
+ *
+ * (c) 1999 Straylight/Edgeware
+ */
+
+/*----- Licensing notice --------------------------------------------------*
+ *
+ * This file is part of the mLib utilities library.
+ *
+ * mLib is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * mLib is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with mLib; if not, write to the Free
+ * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+ * MA 02111-1307, USA.
+ */
+
+#ifndef MLIB_LOCK_H
+#define MLIB_LOCK_H
+
+#ifdef __cplusplus
+  extern "C" {
+#endif
+
+/*----- Magic constants ---------------------------------------------------*/
+
+enum {
+  LOCK_UNLOCK,                         /* Release a lock I obtained */
+  LOCK_EXCL,                           /* Obtain an exclusive lock */
+  LOCK_NONEXCL                         /* Obtain a nonexclusive lock */
+};
+
+/*----- Functions provided ------------------------------------------------*/
+
+/* --- @lock_file@ --- *
+ *
+ * Arguments:  @int fd@ = file descriptor to lock
+ *             @unsigned how@ = type of lock required
+ *
+ * Returns:    0 if OK, -1 if it failed.
+ *
+ * Use:                Acquires a lock on the given file.  The value @how@
+ *             specifies the type of lock to acquire: @LOCK_EXCL@ gets
+ *             an exclusive (write) lock; @LOCK_NONEXCL@ gets a non-
+ *             exclusive (read) lock and @LOCK_UNLOCK@ releases any locks.
+ *             Acquiring a lock gets timed out after a while with an
+ *             error.
+ */
+
+extern int lock_file(int /*fd*/, unsigned /*how*/);
+
+/*----- That's all, folks -------------------------------------------------*/
+
+#ifdef __cplusplus
+  }
+#endif
+
+#endif
diff --git a/sys/mdup.3 b/sys/mdup.3
new file mode 100644 (file)
index 0000000..fc21cd6
--- /dev/null
@@ -0,0 +1,186 @@
+.\" -*-nroff-*-
+.de VS
+.sp 1
+.RS
+.nf
+.ft B
+..
+.de VE
+.ft R
+.fi
+.RE
+.sp 1
+..
+.de hP
+.IP
+.ft B
+\h'-\w'\\$1\ 'u'\\$1\ \c
+.ft P
+..
+.ie t .ds o \(bu
+.el .ds o o
+.TH mdup 3 "4 January" "Straylight/Edgeware" "mLib utilities library"
+.SH NAME
+mdup \- renumber file descriptors
+.SH SYNOPSIS
+.B "#include <mLib/mdup.h>"
+
+.BI "int mdup(mdup_fd *" v ", size_t " n ");"
+.SH DESCRIPTION
+The
+.B mdup
+function renumbers file descriptors, using the
+.BR dup (2)
+and
+.BR dup2 (2)
+system calls.  Its arguments are a pointer
+.I v
+to a vector of
+.B mdup_fd
+structures, and the length
+.I n
+of this vector, in elements.  The
+.B mdup_fd
+structure is defined as
+.VS
+typedef struct mdup_fd {
+  int cur;
+  int want;
+} mdup_fd;
+.VE
+Each `slot' (element) in the vector
+.I v
+represents a file.  The slot's
+.B cur
+member names the current file descriptor for this file; the
+.B want
+member is the file descriptor to move it to.  In order to keep a file
+alive when you don't care which descriptor it ends up with, set
+.I want
+= \-1.  Several slots may specify the same
+.B cur
+descriptor; but they all have to declare different
+.BR want s
+(except that several slots may have
+.I want
+= \-1.
+.PP
+On successful exit, the function will have rearranged the file
+descriptors as requested.  To reflect this, the
+.B cur
+members will all be set to match the
+.B want
+members (except where the latter are \-1).
+.PP
+If there is a failure, then some rearrangement may have been performed
+and some not; the
+.B cur
+members are set to reflect which file descriptors are to be used.
+.PP
+The old file descriptors are
+.IR closed .
+This is different from usual
+.BR dup (2)
+behaviour, of course, but essential for reliable error handling.  If you
+want to keep a particular source file descriptor open as well as make a
+new copy then specify two slots with the same
+.BR cur ,
+one with
+.B want " = " cur
+and one with the desired output descriptor.
+.PP
+The
+.B mdup
+function is capable of arbitrary file descriptor remappings.  In
+particular, it works correctly even if the desired remappings contain
+cycles.
+.SS "Background: the problem that mdup solves"
+The
+.B mdup
+function is intended to be used to adjust file descriptors prior to
+invoking one of the
+.B exec
+system calls.  The standard use of
+.BR dup (2)
+to establish the child process's standard input/output/error files is
+prone to errors in the case where the newly opened file in fact already
+has one of the relevant file descriptors.
+.PP
+Consider the case where we want to run a process with separate pipes
+attached to each of the standard descriptors.  Typical code looks like
+this.
+.VS
+#define P_INIT { \-1, \-1 }
+int p_in[2] = P_INIT, p_out[2] = P_INIT, p_err[2] = P_INIT;
+pid_t kid = -1;
+int i;
+
+if (pipe(p_in) || pipe(p_out) || pipe(p_err)) goto error;
+if ((kid = fork()) < 0) goto error;
+if (!kid) {
+  if (dup2(p_in[0], STDIN_FILENO) < 0 ||
+      dup2(p_out[1], STDOUT_FILENO) < 0 ||
+      dup2(p_err[2], STDERR_FILENO) < 0 ||
+      close(p_in[0]) || close(p_out[0]) || close(p_err[0]) ||
+      close(p_in[1]) || close(p_out[1]) || close(p_err[1]))
+    _exit(127);
+  execvp("/bin/sh", "sh", "-c", "...", (char *)0);
+}
+\&...
+.VE
+Now suppose that, in the parent process, the standard input, output and
+error descriptors are all initially closed.  After the calls to
+.BR pipe (2),
+descriptors 0, 1, and 2 refer to
+.BR p_in[0] ,
+.BR p_in[1] ,
+and
+.B p_out[0]
+respectively.  In the child process, the calls to
+.BR dup2 (2)
+rearrange these.  But then the
+.BR close (2)
+calls will immediately close all three descriptors, before
+.BR exec ing
+the child.
+.PP
+Here's how to rewrite the above function using
+.BR mdup .
+.VS
+#define P_INIT { \-1, \-1 }
+int p_in[2] = P_INIT, p_out[2] = P_INIT, p_err[2] = P_INIT;
+pid_t kid = -1;
+mdup_fd md[3];
+int i;
+
+if (pipe(p_in) || pipe(p_out) || pipe(p_err)) goto error;
+if ((kid = fork()) < 0) goto error;
+if (!kid) {
+  if (close(p_in[1] || close(p_out[0]) || close(p_err[0]))
+    goto _exit(127);
+  md[0].cur = p_in[0];  md[0].want = STDIN_FILENO;
+  md[1].cur = p_out[1]; md[1].want = STDOUT_FILENO;
+  md[2].cur = p_err[1]; md[2].want = STDERR_FILENO;
+  if (mdup(md, 3)) _exit(127);
+  execvp("/bin/sh", "sh", "-c", "...", (char *)0);
+}
+\&...
+.VE
+One can see that, not only is the resulting program more correct, it's
+also simpler.  Note that we close the unwanted ends of the pipes
+.I before
+invoking
+.BR mdup .
+Closing them afterwards risks interfering with the newly assigned
+descriptors which are meant to be passed to the child process.  Note
+also that
+.B mdup
+has taken responsibility for closing the other descriptors for the
+wanted ends of the pipes.
+.SH "SEE ALSO"
+.BR dup (2),
+.BR dup2 (2),
+.BR mLib (3).
+.SH AUTHOR
+Mark Wooding, <mdw@distorted.org.uk>
+
diff --git a/sys/mdup.c b/sys/mdup.c
new file mode 100644 (file)
index 0000000..ee7816a
--- /dev/null
@@ -0,0 +1,516 @@
+/* -*-c-*-
+ *
+ * Duplicate multiple files
+ *
+ * (c) 2008 Straylight/Edgeware
+ */
+
+/*----- Licensing notice --------------------------------------------------*
+ *
+ * This file is part of the mLib utilities library.
+ *
+ * mLib is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * mLib is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with mLib; if not, write to the Free
+ * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+ * MA 02111-1307, USA.
+ */
+
+/*----- Header files ------------------------------------------------------*/
+
+#include <errno.h>
+#include <stdlib.h>
+
+#include <unistd.h>
+
+#include "mdup.h"
+
+/*----- Data structures ---------------------------------------------------*/
+
+typedef struct mdup_fdinfo {
+
+  mdup_fd *f;
+  /* Each @fdinfo@ structure refers to one of the caller's @fd@ structures.
+   * This is it.
+   */
+
+  struct mdup_fdinfo *eqnext, *eqprev;
+  /* The caller's request list can contain more than one entry with any given
+   * @cur@ descriptor.  We group them together into an equivalence class,
+   * which is doubly linked using these fields.
+   */
+
+  struct mdup_fdinfo *up;
+  /* We require that there be at most one node with any given @want@
+   * descriptor (other than @-1@).  There is therefore at most one node whose
+   * @want@ is equal to my @cur@.  If such a node exists, @up@ points to it;
+   * otherwise @up@ is null.
+   */
+
+  struct mdup_fdinfo *down;
+  /* Obviously, @down@ links in the opposite direction from @up@.  However,
+   * there may be several nodes whose @cur@ equals my @want@; therefore
+   * @down@ simply links to one of the nodes in the equivalence class.
+   *
+   * Unsurprisingly, @down@ is the direction we move during the depth-first
+   * traversal phase of the operation.
+   */
+
+  struct mdup_fdinfo *dlink;
+  /* Nodes with @want == -1@, and nodes where we've broken cycles, are
+   * considered `dynamic': their @cur@ has been chosen by @dup@ to be
+   * distinct from any existing descriptor, but may collide with a @want@.
+   * We check each proposed move against the list of dynamic nodes, and move
+   * them out of the way as necessary.  Note that this is really a list of
+   * equivalence classes rather than single nodes.
+   */
+
+  unsigned state;
+  /* The current state of this node.  One of the @ST@ constants described
+   * below.
+   */
+} mdup_fdinfo;
+
+enum {
+  ST_READY,
+  /* Node has not yet been processed.
+   */
+
+  ST_MARK,
+  /* Node has been reached by the depth-first traversal, but its descriptor
+   * has not yet been moved.  This state is used to detect cycles using the
+   * depth-first traversal.
+   */
+
+  ST_DONE,
+  /* Node has been processed completely.  We have @want == -1@ or
+   * @want == cur@.
+   */
+
+  ST_BROKEN,
+  /* Node has been clobbered in order to break a cycle.  The node's
+   * equivalence class has been remapped to a fresh descriptor which (we
+   * hope) is not equal to any node's @want@.  All broken nodes are put on
+   * the dynamic list: if our hope turns out to be misplaced we can remap the
+   * class again.
+   */
+};
+
+/*----- Main code ---------------------------------------------------------*/
+
+/* --- @DO_EQUIVS@ --- *
+ *
+ * Perform @body@ once for each @g@ in the equivalence class of @f@.
+ */
+
+#define DO_EQUIVS(g, f, body) do {                                     \
+  mdup_fdinfo *f_ = (f), *g_ = f_;                                     \
+  do { mdup_fdinfo *g = g_; g_ = g_->eqnext; body; } while (g_ != f_); \
+} while (0)
+
+/* --- @dump@ --- *
+ *
+ * Arguments:  @mdup_fdinfo *v@ = pointer to info vector
+ *             @size_t n@ = size of vector
+ *
+ * Returns:    ---
+ *
+ * Use:                Dumps a scary-looking description of the state of @mdup@'s
+ *             workings.
+ */
+
+#ifdef DEBUG
+
+#include <stdarg.h>
+#include <stdio.h>
+
+#include "macros.h"
+
+#define D(x) x
+
+static void PRINTF_LIKE(4, 5) IGNORABLE
+  dump(mdup_fdinfo *v, size_t n, mdup_fdinfo *dhead, const char *fmt, ...)
+{
+  int i;
+  mdup_fdinfo *f, *g;
+  static const char *state[] = { "READY", "MARK", "DONE", "BROKEN" };
+  va_list ap;
+
+#define INDEX(p) ((p) ? (int)((p) - (v)) : -1)
+
+  /* --- Dump the items, fairly raw --- */
+
+  va_start(ap, fmt);
+  fputs("*** ", stdout);
+  vprintf(fmt, ap);
+  putchar('\n');
+  for (i = 0; i < n; i++) {
+    f = &v[i];
+    printf("%3d: %-6s %3d -> %3d; "
+          "equivs: %3d, %3d; up: %3d; down: %3d; dyn: %3d\n",
+          i, state[f->state], f->f->cur, f->f->want,
+          INDEX(f->eqprev), INDEX(f->eqnext),
+          INDEX(f->up), INDEX(f->down), INDEX(f->dlink));
+  }
+  putchar('\n');
+  va_end(ap);
+
+#undef INDEX
+}
+
+#else
+
+#define D(x)
+
+#endif
+
+/* --- @dfs@ --- *
+ *
+ * Arguments:  @mdup_fdinfo *f@ = which node to process
+ *             @mdup_fdinfo **dhead, ***dtail@ = the dynamic list
+ *
+ * Returns:    Zero on success, @-1@ on some OS failure.
+ *
+ * Use:                Recursive depth-first traversal of the descriptor graph.
+ *
+ *             On exit, the node @f@ will be in state @ST_DONE@ or
+ *             @ST_BROKEN@.
+ */
+
+static int dfs(mdup_fdinfo *f, mdup_fdinfo **dhead, mdup_fdinfo ***dtail)
+{
+  mdup_fdinfo *d;
+  mdup_fd *ff;
+  int can_close_p = 1;
+  int fd, ofd;
+  int e;
+
+  /* --- Null pointers need no processing --- *
+   *
+   * Null pointers mark the end of descending chains.
+   */
+
+  if (!f)
+    return (0);
+
+  /* --- Otherwise our behaviour depends on the node's state --- */
+
+  switch (f->state) {
+
+    /* --- The standard processing, in several phases --- */
+
+    case ST_READY:
+
+      /* --- Mark the class as being in-progress --- */
+
+      DO_EQUIVS(g, f, { g->state = ST_MARK; });
+
+      /* --- Ensure that the our proposed destination is clear --- *
+       *
+       * The depth-first traversal will leave the node in @ST_DONE@ or
+       * @ST_BROKEN@ afterwards; either way, its @cur@ will not be same as
+       * our @want@.
+       *
+       * Note that this can move @%\emph{us}@ to @ST_BROKEN@.  This is not a
+       * significant problem.
+       */
+
+      DO_EQUIVS(g, f, { if (dfs(g->down, dhead, dtail)) return (-1); });
+
+      /* --- Now the real work can begin --- *
+       *
+       * For each node in the class, copy the descriptor from @cur@ to
+       * @want@.  Before doing this, we must move out of the way any (other)
+       * dynamic nodes whose @cur@ matches our @want@.
+       *
+       * Interestingly, this is the only point in the function where we need
+       * nontrivial error handling: if something goes wrong with one of the
+       * @dup2@ calls, we must close the descriptors made so far this pass
+       * before returning.
+       */
+
+      ofd = f->f->cur;
+      DO_EQUIVS(g, f, {
+       ff = g->f;
+       for (d = *dhead; d; d = d->dlink) {
+         if (d != f && d->f->cur == ff->want) {
+           if ((fd = dup(ff->want)) < 0)
+             goto fail;
+           DO_EQUIVS(dd, d, { dd->f->cur = fd; });
+           close(ff->want);
+         }
+       }
+       if (ff->cur == ff->want)
+         can_close_p = 0;
+       else if (dup2(ofd, ff->want) < 0)
+         goto fail;
+       goto ok;
+      fail:
+       e = errno;
+       for (g = g->eqprev; g != f->eqprev; g = g->eqprev) {
+         if (g->f->want != g->f->cur)
+           close(g->f->want);
+       }
+       errno = e;
+       return (-1);
+      ok:;
+      });
+
+      /* --- We're done --- *
+       *
+       * If the original descriptor isn't wanted by anyone we can (and must)
+       * close it.  Nodes can now move to @ST_DONE@.
+       */
+
+      if (can_close_p)
+       close(ofd);
+      DO_EQUIVS(g, f, {
+       g->f->cur = g->f->want;
+       g->state = ST_DONE;
+      });
+      break;
+
+    /* --- We have encoutered a cycle --- *
+     *
+     * The caller wants our descriptor.  We therefore shunt this entire
+     * equivalence class to a new descriptor, and link it onto the dynamic
+     * list.  Mark it as broken so that we don't try to do anything
+     * complicated to it again.
+     */
+
+    case ST_MARK:
+      ofd = f->f->cur;
+      if ((fd = dup(ofd)) < 0)
+       return (-1);
+      DO_EQUIVS(g, f, {
+       g->f->cur = fd;
+       g->state = ST_BROKEN;
+      });
+      f->dlink = **dtail;
+      **dtail = f;
+      close(ofd);
+      break;
+
+    /* --- Nothing to be done here --- *
+     *
+     * @ST_DONE@ nodes have already been completely processed; @ST_BROKEN@
+     * nodes will be fixed up after the main traversal.
+     */
+
+    case ST_DONE:
+    case ST_BROKEN:
+      return (0);
+
+  }
+  return (0);
+}
+
+/* --- @mdup@ --- *
+ *
+ * Arguments:  @mdup_fd *v@ = pointer to @mdup_fd@ vector
+ *             @size_t n@ = size of vector
+ *
+ * Returns:    Zero if successful, @-1@ on failure.
+ *
+ * Use:                Rearranges file descriptors.
+ *
+ *             The vector @v@ consists of a number of @mdup_fd@ structures.
+ *             Each `slot' in the table represents a file.  The slot's @cur@
+ *             member names the current file descriptor for this file; the
+ *             @want@ member is the file descriptor we want to use for it.
+ *             if you want to keep a file alive but don't care which
+ *             descriptor it ends up with, set @want = -1@.  Several slots
+ *             may specify the same @cur@ descriptor; but they all have to
+ *             declare different @want@s (except that several slots may have
+ *             @want = -1@.
+ *
+ *             On successful exit, the function will have rearranged the
+ *             file descriptors as requested.  To reflect this, the @cur@
+ *             members will all be set to match the (non-@-1@) @want@
+ *             members.
+ *
+ *             If there is a failure, then some rearrangement may have been
+ *             performed and some not; the @cur@ members are set to reflect
+ *             which file descriptors are to be used.  The old file
+ *             descriptors are closed.  (This is different from usual @dup@
+ *             behaviour, of course, but essential for reliable error
+ *             handling.)  If you want to keep a particular source file
+ *             descriptor open as well as make a new copy then specify two
+ *             slots with the same @cur@, one with @want = cur@ and one with
+ *             the desired output descriptor.
+ *
+ *             This function works correctly even if the desired remappings
+ *             contain cycles.
+ */
+
+int mdup(mdup_fd *v, size_t n)
+{
+  size_t i, j;
+  mdup_fdinfo *vv;
+  mdup_fdinfo *f, *g, *dhead, **dtail;
+  mdup_fd *ff;
+  int rc = -1;
+  int can_close_p;
+  int ofd, fd;
+
+  /* --- Allocate and initialize the table of info nodes --- *
+   *
+   * Each entry @ff@ in the caller's @v@ array will have a corresponding node
+   * @f@ in @vv@ with @f->f = ff@.  Initially each node's links are null, and
+   * the node is in the @ST_READY@ state.
+   *
+   * We also initialize a list given by @dhead@ and @dtail@ containing the
+   * entries with `dynamically-assigned' descriptors -- i.e., those whose
+   * values we made up using @dup@.  The list lets us detect collisions with
+   * explicitly requested descriptors and move the dynamic ones out of the
+   * way.
+   */
+
+  if ((vv = malloc(sizeof(*vv) * n)) == 0)
+    return (-1);
+
+  dhead = 0;
+  dtail = &dhead;
+  for (i = 0; i < n; i++) {
+    f = &vv[i];
+    f->f = &v[i];
+    f->up = f->down = 0;
+    f->eqnext = f->eqprev = 0;
+    f->state = ST_READY;
+  }
+
+  /* --- Pass one: link the graph together --- *
+   *
+   * Once this pass is complete, the following properties will hold.
+   *
+   *   * The nodes which have the same @cur@ are linked together by their
+   *    @eqnext@ and @eqprev@ fields into a doubly-linked circular list
+   *    representing this equivalence class.
+   *
+   *   * @f->up == g@ if and only if @f->f->cur == g->f->want@.  (Note that
+   *    @want@ fields are unique according to our interface.  We detect
+   *    violations and exit with @errno == EINVAL@.)
+   *
+   *   * If @f->up == g@ then there exists a @ff@ in the same equivalence
+   *    class (and therefore on @f@'s @eqnext@ list) as @f@ with
+   *    @g->down == ff@.
+   */
+
+  for (i = 0; i < n; i++) {
+    f = &vv[i];
+    if (!f->eqnext)
+      f->eqnext = f->eqprev = f;
+    for (j = 0; j < n; j++) {
+      if (i == j)
+       continue;
+      g = &vv[j];
+      if (f->f->cur == g->f->cur) {
+       if (!g->eqnext) {
+         g->eqnext = f->eqnext;
+         g->eqprev = f;
+         f->eqnext->eqprev = g;
+         f->eqnext = g;
+       }
+      }
+      if (g->f->want == -1)
+       /* fine */;
+      else if (f->f->want == g->f->want) {
+       errno = EINVAL;
+       goto fail;
+      } else if (f->f->cur == g->f->want) {
+       f->up = g;
+       if (!g->down)
+         g->down = f;
+      }
+    }
+  }
+
+  /* --- Pass two: handle don't-care requests --- *
+   *
+   * By the end of this pass, we have the following properties.
+   *
+   *   * Every node will be marked @ST_DONE@.  This is a temporary abuse of
+   *    the @ST_DONE@ state which will be rectified during the next pass.
+   *
+   *   * Every node with @want == -1@ will have @cur@ set to a freshly
+   *    allocated file descriptor distinct from every previously open file.
+   */
+
+  for (i = 0; i < n; i++) {
+    f = &vv[i];
+    switch (f->state) {
+      case ST_DONE:
+       break;
+      case ST_READY:
+       can_close_p = 1;
+       DO_EQUIVS(g, f, {
+         ff = g->f;
+         ofd = ff->cur;
+         if (ff->want != -1)
+           can_close_p = 0;
+         else {
+           if ((fd = dup(ofd)) < 0)
+             goto fail;
+           ff->cur = fd;
+         }
+         g->state = ST_DONE;
+       });
+       if (can_close_p)
+         close(ofd);
+       break;
+    }
+  }
+
+  /* --- Pass three: restore equivalence classes and @down@ links --- *
+   *
+   * This pass re-establishes the properties from pass one.  Because we've
+   * changed some @cur@ members, the equivalence classes will have changed,
+   * so we must fix up the @eqnext@ lists and @down@ links.
+   *
+   * Nodes with @want == -1@ are now finished with (modulo tweaking
+   * dynamically allocated descriptors as we process the others), so we leave
+   * them in @ST_DONE@; other nodes are restored to @ST_READY@.
+   */
+
+  for (i = 0; i < n; i++) {
+    f = &vv[i];
+    ff = f->f;
+    if (ff->want == -1) {
+      f->eqnext->eqprev = f->eqprev;
+      f->eqprev->eqnext = f->eqnext;
+      f->eqnext = f->eqprev = f;
+      f->dlink = *dtail;
+      *dtail = f;
+    } else
+      f->state = ST_READY;
+  }
+
+  /* --- Pass four: main depth-first traversal --- *
+   *
+   * See the description of the function @dfs@ above.  After this pass, every
+   * node is in state @ST_DONE@ or @ST_BROKEN@.
+   */
+
+  for (i = 0; i < n; i++) {
+    if (dfs(&vv[i], &dhead, &dtail))
+      goto fail;
+  }
+
+  /* --- Finished --- */
+
+  rc = 0;
+fail:
+  free(vv);
+  return (rc);
+}
+
+/*----- That's all, folks -------------------------------------------------*/
diff --git a/sys/mdup.h b/sys/mdup.h
new file mode 100644 (file)
index 0000000..6223921
--- /dev/null
@@ -0,0 +1,90 @@
+/* -*-c-*-
+ *
+ * Duplicate multiple files
+ *
+ * (c) 2008 Straylight/Edgeware
+ */
+
+/*----- Licensing notice --------------------------------------------------*
+ *
+ * This file is part of the mLib utilities library.
+ *
+ * mLib is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * mLib is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with mLib; if not, write to the Free
+ * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+ * MA 02111-1307, USA.
+ */
+
+#ifndef MDUP_H
+#define MDUP_H
+
+#ifdef __cplusplus
+  extern "C" {
+#endif
+
+/*----- Data structures ---------------------------------------------------*/
+
+typedef struct mdup_fd {
+  int cur;                             /* Current file descriptor */
+  int want;                            /* File descriptor wanted */
+} mdup_fd;
+
+/*----- Functions provided ------------------------------------------------*/
+
+/* --- @mdup@ --- *
+ *
+ * Arguments:  @mdup_fd *v@ = pointer to @mdup_fd@ vector
+ *             @size_t n@ = size of vector
+ *
+ * Returns:    Zero if successful, @-1@ on failure.
+ *
+ * Use:                Rearranges file descriptors.
+ *
+ *             The vector @v@ consists of a number of @mdup_fd@ structures.
+ *             Each `slot' in the table represents a file.  The slot's @cur@
+ *             member names the current file descriptor for this file; the
+ *             @want@ member is the file descriptor we want to use for it.
+ *             if you want to keep a file alive but don't care which
+ *             descriptor it ends up with, set @want = -1@.  Several slots
+ *             may specify the same @cur@ descriptor; but they all have to
+ *             declare different @want@s (except that several slots may have
+ *             @want = -1@.
+ *
+ *             On successful exit, the function will have rearranged the
+ *             file descriptors as requested.  To reflect this, the @cur@
+ *             members will all be set to match the (non-@-1@) @want@
+ *             members.
+ *
+ *             If there is a failure, then some rearrangement may have been
+ *             performed and some not; the @cur@ members are set to reflect
+ *             which file descriptors are to be used.  The old file
+ *             descriptors are closed.  (This is different from usual @dup@
+ *             behaviour, of course, but essential for reliable error
+ *             handling.)  If you want to keep a particular source file
+ *             descriptor open as well as make a new copy then specify two
+ *             slots with the same @cur@, one with @want = cur@ and one with
+ *             the desired output descriptor.
+ *
+ *             This function works correctly even if the desired remappings
+ *             contain cycles.
+ */
+
+extern int mdup(mdup_fd */*v*/, size_t /*n*/);
+
+/*----- That's all, folks -------------------------------------------------*/
+
+#ifdef __cplusplus
+  }
+#endif
+
+#endif
diff --git a/sys/t/fdpass-test.c b/sys/t/fdpass-test.c
new file mode 100644 (file)
index 0000000..54fc8dc
--- /dev/null
@@ -0,0 +1,108 @@
+/* -*-c-*-
+ *
+ * Test the file-descriptor passing code
+ *
+ * (c) 2018 Straylight/Edgeware
+ */
+
+/*----- Licensing notice --------------------------------------------------*
+ *
+ * This file is part of the mLib utilities library.
+ *
+ * mLib is free software: you can redistribute it and/or modify it under
+ * the terms of the GNU Library General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or (at
+ * your option) any later version.
+ *
+ * mLib is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Library General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with mLib.  If not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
+ * USA.
+ */
+
+/*----- Header files ------------------------------------------------------*/
+
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <sys/socket.h>
+#include <sys/un.h>
+
+#include "fdpass.h"
+#include "quis.h"
+#include "report.h"
+
+#undef sun
+
+/*----- Main code ---------------------------------------------------------*/
+
+static void set_unix_addr(struct sockaddr_un *sun, const char *path)
+{
+  if (strlen(path) >= sizeof(sun->sun_path))
+    die(2, "pathname `%s' too long", path);
+  sun->sun_family = AF_UNIX;
+  strcpy(sun->sun_path, path);
+}
+
+const char sockmsg[] = "socket message", pipemsg[] = "pipe message";
+
+int main(int argc, char *argv[])
+{
+  struct sockaddr_un sun;
+  char buf[16];
+  int lsk, sk, fd, pfd[2];
+  socklen_t len;
+  ssize_t n;
+
+  ego(argv[0]);
+
+  if (argc == 3 && STRCMP(argv[1], ==, "server")) {
+    set_unix_addr(&sun, argv[2]);
+    lsk = socket(PF_UNIX, SOCK_STREAM, 0);
+    if (lsk < 0) die(2, "socket: %s", strerror(errno));
+    if (bind(lsk, (struct sockaddr *)&sun, sizeof(sun)))
+      die(2, "bind: %s", strerror(errno));
+    if (listen(lsk, 5)) die(2, "listen: %s", strerror(errno));
+    len = sizeof(sun); sk = accept(lsk, (struct sockaddr *)&sun, &len);
+    if (sk < 0) die(2, "accept: %s", strerror(errno));
+    close(lsk);
+    n = fdpass_recv(sk, &fd, buf, sizeof(buf));
+    if (n < 0) die(2, "fdrecv: %s", strerror(errno));
+    close(sk);
+    if (fd == -1) die(2, "no fd found");
+    if (n != sizeof(sockmsg) || STRCMP(buf, !=, sockmsg))
+      die(2, "socket message mismatch (found `%.*s')", (int)n, buf);
+    n = read(fd, buf, sizeof(buf));
+    if (n < 0) die(2, "read: %s", strerror(errno));
+    if (n != sizeof(pipemsg) || STRCMP(buf, !=, pipemsg))
+      die(2, "pipe message mismatch (found `%.*s')", (int)n, buf);
+    close(fd);
+  } else if (argc == 3 && STRCMP(argv[1], ==, "client")) {
+    set_unix_addr(&sun, argv[2]);
+    if (pipe(pfd)) die(2, "pipe: %s", strerror(errno));
+    sk = socket(PF_UNIX, SOCK_STREAM, 0);
+    if (sk < 0) die(2, "socket: %s", strerror(errno));
+    if (connect(sk, (struct sockaddr *)&sun, sizeof(sun)))
+      die(2, "connect: %s", strerror(errno));
+    if (fdpass_send(sk, pfd[0], sockmsg, sizeof(sockmsg)) < 0)
+      die(2, "fdsend: %s", strerror(errno));
+    close(pfd[0]);
+    close(sk);
+    if (write(pfd[1], pipemsg, sizeof(pipemsg)) < 0)
+      die(2, "write: %s", strerror(errno));
+    close(pfd[1]);
+  } else {
+    pquis(stderr, "usage: $ client|server SOCK\n");
+    exit(2);
+  }
+  return (0);
+}
+
+/*----- That's all, folks -------------------------------------------------*/
diff --git a/sys/t/mdup-test.c b/sys/t/mdup-test.c
new file mode 100644 (file)
index 0000000..6597b33
--- /dev/null
@@ -0,0 +1,87 @@
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include "macros.h"
+#include "mdup.h"
+
+#define MAXFD 256
+
+/* For some reason, Cygwin has started leaving cruft in the top bits of inode
+ * numbers, which breaks this test.  Mask them off.
+ */
+#if defined(__CYGWIN__) && defined(__amd64__)
+#  define HACK_INODE(ino) ((ino)&0xffffffffffff)
+#else
+#  define HACK_INODE(ino) (ino)
+#endif
+
+static void fail(const char *what) { perror(what); exit(1); }
+
+int main(int argc, char *argv[])
+{
+  int i, n, j;
+  int fd, fd2;
+  struct stat st;
+  int ino[MAXFD];
+  int flag[MAXFD];
+  mdup_fd fds[MAXFD];
+  int win = 1;
+  int verbose = 0;
+
+  for (i = 1, j = 0; i < argc; i++) {
+    if (STRCMP(argv[i], ==, "-v")) { verbose++; continue; }
+    if (j >= MAXFD) { fprintf(stderr, "too many\n"); exit(1); }
+    if (sscanf(argv[i], "%d:%d", &fds[j].cur, &fds[j].want) < 2 ||
+       fds[j].cur >= MAXFD)
+      { fprintf(stderr, "bad syntax\n"); exit(1); }
+    j++;
+  }
+  n = j;
+  for (i = 0; i < MAXFD; i++) flag[i] = -1;
+  for (i = 0; i < n; i++) {
+    fd = fds[i].cur;
+    if (flag[fd] >= 0) {
+      ino[i] = ino[flag[fd]];
+      if (verbose)
+       printf("exist fd %d[%d] = ino %lu\n", fd, i, (unsigned long)ino[i]);
+    } else {
+      flag[fd] = i;
+      if ((fd2 = open(",delete-me",
+                     O_WRONLY | O_CREAT | O_EXCL,
+                     0700)) < 0)
+       fail("creat");
+      unlink(",delete-me");
+      if (fd2 != fd) { if (dup2(fd2, fd) < 0) fail("dup2"); close(fd2); }
+      if (fstat(fd, &st)) fail("fstat");
+      ino[i] = HACK_INODE(st.st_ino);
+      if (verbose)
+       printf("open fd %d[%d] = ino %lu\n", fd, i, (unsigned long)ino[i]);
+    }
+  }
+
+  if (mdup(fds, n)) fail("mdup");
+
+  for (i = 0; i < n; i++) {
+    fd = fds[i].cur;
+    if (fds[i].want != -1 && fds[i].want != fd) {
+      printf("fd %d[%d] != %d\n", fd, i, fds[i].want);
+      win = 0;
+    } else if (fstat(fd, &st)) {
+      printf("fstat %d[%d] failed: %s\n", fd, i, strerror(errno));
+      win = 0;
+    } else if (HACK_INODE(st.st_ino) != ino[i]) {
+      if (!verbose) printf("ino %d[%d] wrong\n", fd, i);
+      else printf("ino %d[%d] = %lu != %lu\n", fd, i,
+                 (unsigned long)HACK_INODE(st.st_ino),
+                 (unsigned long)ino[i]);
+      win = 0;
+    }
+  }
+
+  return (!win);
+}
diff --git a/sys/tests.at b/sys/tests.at
new file mode 100644 (file)
index 0000000..da79143
--- /dev/null
@@ -0,0 +1,67 @@
+### -*-autotest-*-
+###
+### Test script for system features
+###
+### (c) 2009 Straylight/Edgeware
+###
+
+###----- Licensing notice ---------------------------------------------------
+###
+### This file is part of the mLib utilities library.
+###
+### mLib is free software; you can redistribute it and/or modify
+### it under the terms of the GNU Library General Public License as
+### published by the Free Software Foundation; either version 2 of the
+### License, or (at your option) any later version.
+###
+### mLib is distributed in the hope that it will be useful,
+### but WITHOUT ANY WARRANTY; without even the implied warranty of
+### MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+### GNU Library General Public License for more details.
+###
+### You should have received a copy of the GNU Library General Public
+### License along with mLib; if not, write to the Free
+### Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+### MA 02111-1307, USA.
+
+###--------------------------------------------------------------------------
+### fdpass
+
+AT_SETUP([sys: fdpass])
+AT_KEYWORDS([sys fdpass])
+
+fdpass=BUILDDIR/t/fdpass.t
+
+AT_CHECK([
+  $fdpass server sock&
+  while ! [[ -S sock ]]; do sleep 1; done
+  $fdpass client sock && wait])
+
+AT_CLEANUP
+
+###--------------------------------------------------------------------------
+### mdup
+
+AT_SETUP([sys: mdup])
+AT_KEYWORDS([sys mdup])
+
+mdup=BUILDDIR/t/mdup.t
+
+## Very simple cases.
+AT_CHECK([$mdup 3:4])
+AT_CHECK([$mdup 4:3])
+
+## Overlapping sources and destinations.
+AT_CHECK([$mdup 4:3 3:5 5:6])
+
+## Repeated sources.
+AT_CHECK([$mdup 3:4 3:3 3:-1])
+AT_CHECK([$mdup 5:8 3:4 3:5 4:6])
+
+## Cycles.
+AT_CHECK([$mdup 5:7 3:4 3:5 4:6 5:3])
+AT_CHECK([$mdup 5:8 3:4 3:5 4:6 5:3])
+
+AT_CLEANUP
+
+###----- That's all, folks --------------------------------------------------
diff --git a/sys/tv.3 b/sys/tv.3
new file mode 100644 (file)
index 0000000..62d9189
--- /dev/null
+++ b/sys/tv.3
@@ -0,0 +1,174 @@
+.\" -*-nroff-*-
+.TH tv 3 "22 May 1999" "Straylight/Edgeware" "mLib utilities library"
+.SH NAME
+tv \- arithmetic on \fBstruct timeval\fR objects
+.\" @tv_add
+.\" @tv_addl
+.\" @tv_sub
+.\" @tv_subl
+.\" @tv_cmp
+.\"
+.\" @TV_ADD
+.\" @TV_ADDL
+.\" @TV_SUB
+.\" @TV_SUBL
+.\" @TV_CMP
+.\"
+.SH SYNOPSIS
+.nf
+.B "#include <mLib/tv.h>"
+
+.BI "void tv_add(struct timeval *" dst ,
+.BI "            const struct timeval *" a ,
+.BI "            const struct timeval *" b );
+.BI "void tv_addl(struct timeval *" dst ,
+.BI "             const struct timeval *" a ,
+.BI "             time_t " sec ", unsigned long " usec );
+.BI "void tv_sub(struct timeval *" dst ,
+.BI "            const struct timeval *" a ,
+.BI "            const struct timeval *" b );
+.BI "void tv_subl(struct timeval *" dst ,
+.BI "             const struct timeval *" a ,
+.BI "             time_t " sec ", unsigned long " usec );
+.BI "int tv_cmp(const struct timeval *" a ,
+.BI "           const struct timeval *" b );
+
+.B "int MILLION;"
+.BI "void TV_ADD(struct timeval *" dst ,
+.BI "            const struct timeval *" a ,
+.BI "            const struct timeval *" b );
+.BI "void TV_ADDL(struct timeval *" dst ,
+.BI "             const struct timeval *" a ,
+.BI "             time_t " sec ", unsigned long " usec );
+.BI "void TV_SUB(struct timeval *" dst ,
+.BI "            const struct timeval *" a ,
+.BI "            const struct timeval *" b );
+.BI "void TV_SUBL(struct timeval *" dst ,
+.BI "             const struct timeval *" a ,
+.BI "             time_t " sec ", unsigned long " usec );
+.BI "int TV_CMP(const struct timeval *" a ", " op ,
+.BI "           const struct timeval *" b );
+.fi
+.SH DESCRIPTION
+The
+.B <mLib/tv.h>
+header file provides functions and macros which perform simple
+arithmetic on objects of type
+.BR "struct timeval" ,
+which is capable of representing times to microsecond precision.
+See your
+.BR gettimeofday (2)
+or
+.BR select (2)
+manpages for details of this structure.
+.PP
+The macros are the recommended interface to
+.BR tv 's
+facilities.  The function interface is provided for compatibility
+reasons, and for bizarre cases when macros won't do the job.
+.PP
+The main arithmetic functions are in three-address form: they accept two
+source arguments and a separate destination argument (which may be the
+same as one or even both of the source arguments).  The destination is
+written before the sources.  All the arguments are pointers to the
+actual structures.
+.PP
+.BI TV_ADD( d ", " x ", " y )
+adds
+.BR timeval s
+.I x
+and
+.I y
+storing the result in
+.IR d .
+Similarly,
+.BI TV_SUB( d ", " x ", " y )
+subtracts
+.I y
+from
+.I x
+storing the result in
+.IR d .
+.PP
+The macros
+.B TV_ADDL
+and
+.B TV_SUBL
+work the same way as
+.B TV_ADD
+and
+.B TV_SUB
+respectively, except their second source operand is expressed
+immediately as two integers arguments expressing a time in seconds and
+microseconds respectively.  Hence,
+.BI TV_ADDL( d ", " s ", 3, 250000)"
+adds 3.25 seconds to
+.I s
+and puts the result in
+.IR d .
+Similarly,
+.BI TV_SUBL( d ", " s ", 3, 250000)"
+subtracts 3.25 seconds from
+.I s
+and puts the answer in
+.IR d .
+.PP
+The function equivalents for the above arithmetic macros work in exactly
+the same way (and indeed have trivial implementations in terms of the
+macros).  The name of the function corresponding to a macro is simply
+the macro name in lower-case.
+.PP
+Two
+.B timeval
+objects can be compared using the
+.B TV_CMP
+macro.  If
+.I op
+is a relational operator (e.g.,
+.RB ` == ',
+.RB ` < ',
+or
+.RB ` >= ')
+then
+.BI TV_CMP( x ", " op ", " y )
+is one when
+.I "x op y"
+is true and zero otherwise.
+.PP
+The function
+.B tv_cmp
+works differently.  Given two arguments
+.I x
+and
+.IR y ,
+it returns -1 if
+.IR x " < " y ,
+zero if
+.IR x " == " y ,
+or 1 if
+.IR x " > " y .
+Hence, the result can be compared against zero in a relatively intuitive
+way (as for
+.BR strcmp (3),
+except that because the results are defined more tightly, it's possible
+to use a
+.B switch
+on the result).
+.SH ACKNOWLEDGEMENT
+The idea of passing a relational operator to
+.B TV_CMP
+is stolen from the
+.B timercmp
+macro in the GNU C library.  This doesn't look like a GNU original,
+however; whatever, it doesn't seem to be very portable.  The GNU
+.B timercmp
+macro had a warning attached to it that it wouldn't work for operators
+like
+.RB ` <= ',
+although I can't see why there'd be a problem.  (If there is one, then
+my implementation has it too, because they're the same.  I don't
+document the restriction because I don't think it exists.)
+.SH "SEE ALSO"
+.BR mLib (3).
+.SH AUTHOR
+Mark Wooding, <mdw@distorted.org.uk>
diff --git a/sys/tv.c b/sys/tv.c
new file mode 100644 (file)
index 0000000..dca58cc
--- /dev/null
+++ b/sys/tv.c
@@ -0,0 +1,126 @@
+/* -*-c-*-
+ *
+ * Manipulation of timeval structures
+ *
+ * (c) 1998 Straylight/Edgeware
+ */
+
+/*----- Licensing notice --------------------------------------------------*
+ *
+ * This file is part of the mLib utilities library.
+ *
+ * mLib is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * mLib is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with mLib; if not, write to the Free
+ * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+ * MA 02111-1307, USA.
+ */
+
+/*----- Header files ------------------------------------------------------*/
+
+#include <sys/time.h>
+#include "tv.h"
+
+/*----- Main code ---------------------------------------------------------*/
+
+/* --- @tv_add@ --- *
+ *
+ * Arguments:  @struct timeval *dst@ = destination block
+ *             @const struct timeval *a, *b@ = source blocks
+ *
+ * Returns:    ---
+ *
+ * Use:                Adds two timevals.
+ */
+
+void tv_add(struct timeval *dst,
+           const struct timeval *a, const struct timeval *b)
+{
+  TV_ADD(dst, a, b);
+}
+
+/* --- @tv_addl@ --- *
+ *
+ * Arguments:  @struct timeval *dst@ = destination block
+ *             @const struct timeval *a@ = source blocks
+ *             @time_t sec@, @unsigned long usec@ = time to add
+ *
+ * Returns:    ---
+ *
+ * Use:                Adds a literal time in seconds and microseconds.
+ */
+
+void tv_addl(struct timeval *dst, const struct timeval *a,
+            time_t sec, unsigned long usec)
+{
+  TV_ADDL(dst, a, sec, usec);
+}
+
+/* --- @tv_sub@ --- *
+ *
+ * Arguments:  @struct timeval *dst@ = destination block
+ *             @const struct timeval *a, *b@ = source blocks
+ *
+ * Returns:    ---
+ *
+ * Use:                Subtracts two timevals.
+ */
+
+void tv_sub(struct timeval *dst,
+           const struct timeval *a, const struct timeval *b)
+{
+  TV_SUB(dst, a, b);
+}
+
+/* --- @tv_subl@ --- *
+ *
+ * Arguments:  @struct timeval *dst@ = destination block
+ *             @const struct timeval *a@ = source blocks
+ *             @time_t sec@, @unsigned long usec@ = time to subtract
+ *
+ * Returns:    ---
+ *
+ * Use:                Subtracts a literal time in seconds and microseconds.
+ */
+
+void tv_subl(struct timeval *dst, const struct timeval *a,
+                   time_t sec, unsigned long usec)
+{
+  TV_SUBL(dst, a, sec, usec);
+}
+
+/* --- @tv_cmp@ --- *
+ *
+ * Arguments:  @const struct timeval *a, *b@ = source blocks
+ *
+ * Returns:    Less than, equal to, or greater than zero.
+ *
+ * Use:                Compares two timevals.
+ */
+
+int tv_cmp(const struct timeval *a, const struct timeval *b)
+{
+  /* --- This is more awkward than the case the macro deals with --- */
+
+  if (a->tv_sec > b->tv_sec)
+    return (1);
+  else if (a->tv_sec < b->tv_sec)
+    return (-1);
+  else if (a->tv_usec > b->tv_usec)
+    return (1);
+  else if (a->tv_usec < b->tv_usec)
+    return (-1);
+  else
+    return (0);
+}
+
+/*----- That's all, folks -------------------------------------------------*/
diff --git a/sys/tv.h b/sys/tv.h
new file mode 100644 (file)
index 0000000..61f0a99
--- /dev/null
+++ b/sys/tv.h
@@ -0,0 +1,148 @@
+/* -*-c-*-
+ *
+ * Manipulation of timeval structures
+ *
+ * (c) 1998 Straylight/Edgeware
+ */
+
+/*----- Licensing notice --------------------------------------------------*
+ *
+ * This file is part of the mLib utilities library.
+ *
+ * mLib is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * mLib is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with mLib; if not, write to the Free
+ * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+ * MA 02111-1307, USA.
+ */
+
+#ifndef MLIB_TV_H
+#define MLIB_TV_H
+
+#ifdef __cplusplus
+  extern "C" {
+#endif
+
+/*----- Header files ------------------------------------------------------*/
+
+#include <sys/time.h>
+
+/*----- A macro to make reading easier ------------------------------------*/
+
+#define MILLION 1000000
+
+/*----- Functions provided ------------------------------------------------*/
+
+/* --- @tv_add@ --- *
+ *
+ * Arguments:  @struct timeval *dst@ = destination block
+ *             @const struct timeval *a, *b@ = source blocks
+ *
+ * Returns:    ---
+ *
+ * Use:                Adds two timevals.
+ */
+
+extern void tv_add(struct timeval */*dst*/,
+                  const struct timeval */*a*/,
+                  const struct timeval */*b*/);
+
+#define TV_ADD(dst, a, b) TV_ADDL(dst, a, (b)->tv_sec, (b)->tv_usec)
+
+/* --- @tv_addl@ --- *
+ *
+ * Arguments:  @struct timeval *dst@ = destination block
+ *             @const struct timeval *a@ = source blocks
+ *             @time_t sec@, @unsigned long usec@ = time to add
+ *
+ * Returns:    ---
+ *
+ * Use:                Adds a literal time in seconds and microseconds.
+ */
+
+extern void tv_addl(struct timeval */*dst*/,
+                   const struct timeval */*a*/,
+                   time_t /*sec*/, unsigned long /*usec*/);
+
+#define TV_ADDL(dst, a, sec, usec) do {                                        \
+  (dst)->tv_sec = (a)->tv_sec + (sec);                                 \
+  (dst)->tv_usec = (a)->tv_usec + (usec);                              \
+  if ((dst)->tv_usec >= MILLION) {                                     \
+    (dst)->tv_usec -= MILLION;                                         \
+    (dst)->tv_sec++;                                                   \
+  }                                                                    \
+} while (0)
+
+/* --- @tv_sub@ --- *
+ *
+ * Arguments:  @struct timeval *dst@ = destination block
+ *             @const struct timeval *a, *b@ = source blocks
+ *
+ * Returns:    ---
+ *
+ * Use:                Subtracts two timevals.
+ */
+
+extern void tv_sub(struct timeval */*dst*/,
+                  const struct timeval */*a*/,
+                  const struct timeval */*b*/);
+
+#define TV_SUB(dst, a, b) TV_SUBL(dst, a, (b)->tv_sec, (b)->tv_usec)
+
+/* --- @tv_subl@ --- *
+ *
+ * Arguments:  @struct timeval *dst@ = destination block
+ *             @const struct timeval *a@ = source blocks
+ *             @time_t sec@, @unsigned long usec@ = time to subtract
+ *
+ * Returns:    ---
+ *
+ * Use:                Subtracts a literal time in seconds and microseconds.
+ */
+
+extern void tv_subl(struct timeval */*dst*/,
+                   const struct timeval */*a*/,
+                   time_t /*sec*/, unsigned long /*usec*/);
+
+#define TV_SUBL(dst, a, sec, usec) do {                                        \
+  (dst)->tv_sec = (a)->tv_sec - (sec);                                 \
+  if ((a)->tv_usec >= (usec))                                          \
+    (dst)->tv_usec = (a)->tv_usec - (usec);                            \
+  else {                                                               \
+    (dst)->tv_usec = (a)->tv_usec + MILLION - (usec);                  \
+    (dst)->tv_sec--;                                                   \
+  }                                                                    \
+} while (0)
+
+/* --- @tv_cmp@ --- *
+ *
+ * Arguments:  @const struct timeval *a, *b@ = source blocks
+ *
+ * Returns:    Less than, equal to, or greater than zero.
+ *
+ * Use:                Compares two timevals.
+ */
+
+extern int tv_cmp(const struct timeval */*a*/,
+                 const struct timeval */*b*/);
+
+#define TV_CMP(a, op, b) ((a)->tv_sec == (b)->tv_sec ?                 \
+                           (a)->tv_usec op (b)->tv_usec :              \
+                           (a)->tv_sec op (b)->tv_sec)
+
+/*----- That's all, folks -------------------------------------------------*/
+
+#ifdef __cplusplus
+  }
+#endif
+
+#endif
diff --git a/t/Makefile.am b/t/Makefile.am
new file mode 100644 (file)
index 0000000..617af72
--- /dev/null
@@ -0,0 +1,48 @@
+### -*-makefile-*-
+###
+### Build script for test infrastructure
+###
+### (c) 2009 Straylight/Edgeware
+###
+
+###----- Licensing notice ---------------------------------------------------
+###
+### This file is part of the mLib utilities library.
+###
+### mLib is free software; you can redistribute it and/or modify
+### it under the terms of the GNU Library General Public License as
+### published by the Free Software Foundation; either version 2 of the
+### License, or (at your option) any later version.
+###
+### mLib is distributed in the hope that it will be useful,
+### but WITHOUT ANY WARRANTY; without even the implied warranty of
+### MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+### GNU Library General Public License for more details.
+###
+### You should have received a copy of the GNU Library General Public
+### License along with mLib; if not, write to the Free
+### Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+### MA 02111-1307, USA.
+
+include autotest.am
+
+autotest_TESTS          =
+
+###--------------------------------------------------------------------------
+### Make sure everything is built.
+
+check-local: check-build
+check-build:
+       $(MAKE) -C $(top_builddir) all
+.PHONY: check-build
+
+###--------------------------------------------------------------------------
+### Test directories.
+
+autotest_TESTS         += $(top_srcdir)/codec/tests.at
+autotest_TESTS         += $(top_srcdir)/hash/tests.at
+autotest_TESTS         += $(top_srcdir)/struct/tests.at
+autotest_TESTS         += $(top_srcdir)/sys/tests.at
+autotest_TESTS         += $(top_srcdir)/utils/tests.at
+
+###----- That's all, folks --------------------------------------------------
diff --git a/t/atlocal.in b/t/atlocal.in
new file mode 100644 (file)
index 0000000..1a4a661
--- /dev/null
@@ -0,0 +1,29 @@
+### -*-sh-*-
+###
+### Configuration variables interesting to the test suite
+###
+### (c) 2009 Straylight/Edgeware
+###
+
+###----- Licensing notice ---------------------------------------------------
+###
+### This file is part of the mLib utilities library.
+###
+### mLib is free software; you can redistribute it and/or modify
+### it under the terms of the GNU Library General Public License as
+### published by the Free Software Foundation; either version 2 of the
+### License, or (at your option) any later version.
+###
+### mLib is distributed in the hope that it will be useful,
+### but WITHOUT ANY WARRANTY; without even the implied warranty of
+### MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+### GNU Library General Public License for more details.
+###
+### You should have received a copy of the GNU Library General Public
+### License along with mLib; if not, write to the Free
+### Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+### MA 02111-1307, USA.
+
+PYTHON=@PYTHON@
+
+###----- That's all, folks --------------------------------------------------
diff --git a/test/Makefile.am b/test/Makefile.am
new file mode 100644 (file)
index 0000000..e81daed
--- /dev/null
@@ -0,0 +1,40 @@
+### -*-makefile-*-
+###
+### Build script for testing
+###
+### (c) 2009 Straylight/Edgeware
+###
+
+###----- Licensing notice ---------------------------------------------------
+###
+### This file is part of the mLib utilities library.
+###
+### mLib is free software; you can redistribute it and/or modify
+### it under the terms of the GNU Library General Public License as
+### published by the Free Software Foundation; either version 2 of the
+### License, or (at your option) any later version.
+###
+### mLib is distributed in the hope that it will be useful,
+### but WITHOUT ANY WARRANTY; without even the implied warranty of
+### MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+### GNU Library General Public License for more details.
+###
+### You should have received a copy of the GNU Library General Public
+### License along with mLib; if not, write to the Free
+### Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+### MA 02111-1307, USA.
+
+include $(top_srcdir)/vars.am
+
+noinst_LTLIBRARIES      = libtest.la
+libtest_la_SOURCES      =
+
+###--------------------------------------------------------------------------
+### Component files.
+
+## Testing.
+pkginclude_HEADERS     += testrig.h
+libtest_la_SOURCES     += testrig.c
+LIBMANS                        += testrig.3
+
+###----- That's all, folks --------------------------------------------------
diff --git a/test/testrig.3 b/test/testrig.3
new file mode 100644 (file)
index 0000000..b996617
--- /dev/null
@@ -0,0 +1,244 @@
+.\" -*-nroff-*-
+.de VS
+.sp 1
+.in +5
+.nf
+.ft B
+..
+.de VE
+.ft R
+.fi
+.in -5
+.sp 1
+..
+.TH testrig 3 "5 June 1999" "Straylight/Edgeware" "mLib utilities library"
+.SH NAME
+testrig \- generic test rig
+.\" @test_run
+.SH SYNOPSIS
+.nf
+.B "#include <mLib/testrig.h>"
+
+.BI "void test_do(const test_suite " suite [],
+.BI "             FILE *" fp ,
+.BI "             test_results *" results );
+.BI "void test_run(int " argc ", char *" argv [],
+.BI "              const test_chunk " chunk [],
+.BI "              const char *" def );
+.fi
+.SH DESCRIPTION
+.SS Structure
+Test vectors are gathered together into
+.I chunks
+which should be processed in the same way.  Chunks, in turn, are grouped
+into
+.IR suites .
+.SS Functions
+The
+.B test_do
+function runs a collection of tests, as defined by
+.IR suite ,
+given the test vectors in the file
+.IR fp .
+It returns results in the
+.B test_results
+structure
+.IR results ,
+which has two members:
+.TP
+.B "unsigned tests"
+counts the number of tests carried out, and
+.TP
+.B "unsigned failed"
+counts the number of tests which failed.
+.PP
+The function returns negative if there was a system error or the test
+vector file was corrupt in some way, zero if all the tests were
+successful, or positive if some tests failed.
+.PP
+The
+.B test_run
+provides a simple command-line interface to the test system.  It is
+intended to be called from the
+.B main
+function of a test rig program to check that a particular function or
+suite of functions are running properly.  It does not return.  The arguments
+.I argc
+and
+.I argv
+should just be the arguments given to
+.BR main .
+The
+.I def
+argument gives the name of the default file of test vectors to read.
+This can be overridden at run-time by passing the program a
+.B \-f
+command-line option.  The
+.I chunk
+argument is (the address of) an array of
+.I "chunk definitions"
+describing the layout of the test vector file.
+.SS "Test vector file syntax"
+Test vector files are mostly free-form.  Comments begin with a hash
+.RB (` # ')
+and extend to the end of the line.  Apart from that, newline characters
+are just considered to be whitespace.
+.PP
+Test vector files have the following syntax:
+.PP
+.I file
+::=
+.RI [ suite-header | chunk " ...]"
+.br
+.I suite-header
+::=
+.B suite
+.I name
+.br
+.I chunk
+::=
+.I name
+.B {
+.RI [ test-vector " ...]"
+.B }
+.br
+.I test-vector
+::=
+.RI [ value ...]
+.B ;
+.PP
+Briefly in English: a test vector file is divided into chunks, each of
+which consists of a textual name and a brace-enclosed list of test
+vectors.  Each test vector consists of a number of values terminated by
+a semicolon.
+.PP
+A value is either a sequence of
+.I "word characters"
+(alphanumerics and some other characters)
+or a string enclosed in quote marks (double or single).  Quoted strings
+may contain newlines.  In either type of value, a backslash escapes the
+following character.
+.SS "Suite definitions"
+A
+.I suite definition
+is described by the structure
+.VS
+typedef struct test_suite {
+  const char *name;             /* Name of this suite */
+  const test_chunk *chunks;     /* Pointer to chunks */
+} test_suite;
+.VE
+The
+.I suite
+argument to
+.B test_do
+is a pointer to an array of these structures, terminated by one with a
+null
+.BR name .
+.SS "Chunk definitions"
+A
+.I "chunk definition"
+describes the format of a named chunk: the number and type of the values
+required and the function to call in order to test the system against
+that test vector.  The array is terminated by a chunk definition whose
+name field is a null pointer.
+.PP
+A chunk definition is described by the following structure:
+.VS
+typedef struct test_chunk {
+  const char *name;             /* Name of this chunk */
+  int (*test)(dstr dv[]);       /* Test verification function */
+  test_type *f[TEST_FIELDMAX];  /* Field definitions */
+} test_chunk;
+.VE
+The members of this structure are as follows:
+.TP
+.B "const char *name"
+The name of the chunk described by this chunk definition, or null if
+this is the termination marker.
+.TP
+.B "int (*test)(dstr dv[])"
+The test function.  It is passed an array of dynamic strings, one for
+each field, and must return nonzero if the test succeeded or zero if the
+test failed.  On success, the function should not write anything to
+stdout or stderr; on failure, a report of the test arguments should be
+emitted to stderr.
+.TP
+.B "test_type *f[TEST_FIELDMAX]"
+Definitions of the fields.  This is an array of pointers to
+.I "field types"
+(see below), terminated by a null pointer.
+.PP
+When the test driver encounters a chunk it has a definition for, it
+reads test vectors one by one, translating each value according to the
+designated field type, and then passing the completed array of fields to
+the test function.
+.SS "Field types"
+A field type describes how a field is to be read and written.  A field
+type is described by a structure:
+.VS
+typedef struct test_type {
+  void (*cvt)(const char *buf, dstr *d);
+  void (*dump)(dstr *d, FILE *fp);
+} test_type;
+.VE
+The
+.B cvt
+member is a function called to read an input string stored in
+.B buf
+and output internal-format data in the dynamic string
+.IR d .
+The testrig driver has already stripped of quotes and dealt with
+backslash escapes.
+The
+.B dump
+member is called to write the internal-format data in dynamic string
+.I d
+to the
+.B stdio
+stream
+.IR fp .
+.PP
+There are three predefined field types:
+.TP
+.B "type_string"
+The simplest type.  The string contents is not interpreted at all.
+.TP
+.B "type_hex"
+The string is interpreted as binary data encoded as hexadecimal.
+.TP
+.B "type_int"
+The string is interpreted as a textual representation of an integer.
+The integer is written to the dynamic string, and can be read out again
+with the expression
+.VS
+*(int *)d.buf
+.VE
+which isn't pretty but does the job.
+.TP
+.B "type_long"
+As for
+.B type_int
+but reads and stores a
+.B long
+instead.
+.TP
+.B "type_ulong"
+As for
+.B type_long
+but reads and stores an
+.B "unsigned long"
+instead.
+.TP
+.B "type_uint32"
+As for
+.B type_int
+but reads and stores a
+.B uint32
+(see
+.BR bits (3))
+instead.
+.SH "SEE ALSO"
+.BR mLib (3).
+.SH "AUTHOR"
+Mark Wooding, <mdw@distorted.org.uk>
diff --git a/test/testrig.c b/test/testrig.c
new file mode 100644 (file)
index 0000000..c7dea67
--- /dev/null
@@ -0,0 +1,532 @@
+/* -*-c-*-
+ *
+ * Generic test driver
+ *
+ * (c) 1998 Straylight/Edgeware
+ */
+
+/*----- Licensing notice --------------------------------------------------*
+ *
+ * This file is part of the mLib utilities library.
+ *
+ * mLib is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * mLib is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with mLib; if not, write to the Free
+ * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+ * MA 02111-1307, USA.
+ */
+
+/*----- Header files ------------------------------------------------------*/
+
+#include <ctype.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "dstr.h"
+#include "macros.h"
+#include "report.h"
+#include "quis.h"
+#include "testrig.h"
+
+/*----- Static variables --------------------------------------------------*/
+
+static dstr tok = DSTR_INIT;
+
+enum {
+  TOK_EOF = 0x100,
+  TOK_WORD
+};
+
+/*----- Main code ---------------------------------------------------------*/
+
+/* --- @decode@ --- *
+ *
+ * Arguments:  @int tok@ = token type to decode
+ *
+ * Returns:    Pointer to a textual representation of the token.
+ *
+ * Use:                Produces a readable representation of a token.
+ */
+
+static const char *decode(int t)
+{
+  static char buf[4];
+
+  switch (t) {
+    case TOK_EOF:
+      return ("<eof>");
+    case TOK_WORD:
+      return (tok.buf);
+    default:
+      buf[0] = t;
+      buf[1] = 0;
+      return (buf);
+  }
+  return ("<buggy-program>");
+}
+
+/* --- @gettok@ --- *
+ *
+ * Arguments:  @FILE *fp@ = file handle to read from
+ *
+ * Returns:    Type of token read.
+ *
+ * Use:                Reads a token from the input stream.
+ */
+
+static int gettok(FILE *fp)
+{
+  int ch;
+
+  /* --- Clear the token accumulator --- */
+
+  DRESET(&tok);
+
+  /* --- Prime the lookahead character --- */
+
+again:
+  ch = getc(fp);
+
+  /* --- Skip leading whitespace --- */
+
+  while (ISSPACE(ch))
+    ch = getc(fp);
+
+  /* --- Trap some special characters --- */
+
+  switch (ch) {
+
+    /* --- Comments --- */
+
+    case '#':
+      do ch = getc(fp); while (ch != EOF && ch != '\n');
+      goto again;
+
+    /* --- End of file --- */
+
+    case EOF:
+      return (TOK_EOF);
+
+    /* --- Quote characters --- */
+
+    case '`':
+      ch = '\'';
+    case '\'':
+    case '\"': {
+      int quote = ch;
+
+      for (;;) {
+       ch = getc(fp);
+       if (ch == EOF || ch == quote)
+         break;
+       if (ch == '\\') {
+         ch = getc(fp);
+         if (ch == EOF)
+           ch = '\\';
+       }
+       DPUTC(&tok, ch);
+      }
+      DPUTZ(&tok);
+      return (TOK_WORD);
+    }
+
+    /* --- Self-delimiting things --- */
+
+    case ';':
+    case '{':
+    case '}':
+      return (ch);
+
+    /* --- Anything else is a word --- */
+
+    default:
+      for (;;) {
+       DPUTC(&tok, ch);
+       ch = getc(fp);
+       switch (ch) {
+         case EOF:
+         case ';':
+         case '{':
+         case '}':
+         case '\"':
+         case '\'':
+         case '`':
+           goto done;
+         default:
+           if (ISSPACE(ch))
+             goto done;
+       }
+       if (ch == '\\') {
+         ch = getc(fp);
+         if (ch == EOF)
+           ch = '\\';
+       }
+      }
+    done:
+      ungetc(ch, fp);
+      DPUTZ(&tok);
+      return (TOK_WORD);
+  }
+}
+
+/* --- @type_hex@ --- */
+
+static void cvt_hex(const char *s, dstr *d)
+{
+  while (s[0] && s[1]) {
+    int x = s[0], y = s[1];
+    if ('0' <= x && x <= '9') x -= '0';
+    else if ('A' <= x && x <= 'F') x -= 'A' - 10;
+    else if ('a' <= x && x <= 'f') x -= 'a' - 10;
+    else x = 0;
+    if ('0' <= y && y <= '9') y -= '0';
+    else if ('A' <= y && y <= 'F') y -= 'A' - 10;
+    else if ('a' <= y && y <= 'f') y -= 'a' - 10;
+    else y = 0;
+    DPUTC(d, (x << 4) + y);
+    s += 2;
+  }
+}
+
+static void dump_hex(dstr *d, FILE *fp)
+{
+  const char *p, *q;
+  for (p = d->buf, q = p + d->len; p < q; p++)
+    fprintf(fp, "%02x", *(unsigned char *)p);
+}
+
+const test_type type_hex = { cvt_hex, dump_hex };
+
+/* --- @type_string@ --- */
+
+static void cvt_string(const char *s, dstr *d)
+{
+  DPUTS(d, s);
+}
+
+static void dump_string(dstr *d, FILE *fp)
+{
+  DWRITE(d, fp);
+}
+
+const test_type type_string = { cvt_string, dump_string };
+
+/* --- @type_int@ --- */
+
+static void cvt_int(const char *s, dstr *d)
+{
+  DENSURE(d, sizeof(int));
+  sscanf(s, "%i", (int *)d->buf);
+}
+
+static void dump_int(dstr *d, FILE *fp)
+{
+  fprintf(fp, "%i", *(int *)d->buf);
+}
+
+const test_type type_int = { cvt_int, dump_int };
+
+/* --- @type_long@ --- */
+
+static void cvt_long(const char *s, dstr *d)
+{
+  DENSURE(d, sizeof(long));
+  *(long *)d->buf = strtol(s, 0, 0);
+}
+
+static void dump_long(dstr *d, FILE *fp)
+{
+  fprintf(fp, "%li", *(long *)d->buf);
+}
+
+const test_type type_long = { cvt_long, dump_long };
+
+/* --- @type_ulong@ --- */
+
+static void cvt_ulong(const char *s, dstr *d)
+{
+  DENSURE(d, sizeof(unsigned long));
+  *(unsigned long *)d->buf = strtoul(s, 0, 0);
+}
+
+static void dump_ulong(dstr *d, FILE *fp)
+{
+  fprintf(fp, "%lu", *(unsigned long *)d->buf);
+}
+
+const test_type type_ulong = { cvt_ulong, dump_ulong };
+
+/* --- @type_uint32@ --- */
+
+static void cvt_uint32(const char *buf, dstr *d)
+{
+  DENSURE(d, sizeof(uint32));
+  *(uint32 *)d->buf = strtoul(buf, 0, 0);
+}
+
+static void dump_uint32(dstr *d, FILE *fp)
+{
+  fprintf(fp, "%lu\n", (unsigned long)*(uint32 *)d->buf);
+}
+
+const test_type type_uint32 = { cvt_uint32, dump_uint32 };
+
+/* --- @test_do@ --- *
+ *
+ * Arguments:  @const test_suite suites[]@ = pointer to suite definitions
+ *             @FILE *fp@ = test vector file, ready opened
+ *             @test_results *results@ = where to put results
+ *
+ * Returns:    Negative if something bad happened, or the number of
+ *             failures.
+ *
+ * Use:                Runs a collection of tests against a file of test vectors and
+ *             reports the results.
+ */
+
+int test_do(const test_suite suites[], FILE *fp, test_results *results)
+{
+  test_results dummy;
+  dstr dv[TEST_FIELDMAX];
+  const test_suite *ss;
+  const test_chunk *chunks = suites[0].chunks;
+  const test_chunk *cch;
+  int rc = -1;
+  int ok;
+  int i;
+
+  for (i = 0; i < TEST_FIELDMAX; i++)
+    DCREATE(&dv[i]);
+
+  if (!results)
+    results = &dummy;
+  results->tests = 0;
+  results->failed = 0;
+
+  for (;;) {
+    int t = gettok(fp);
+
+    /* --- This is a reasonable place to stop --- */
+
+    if (t == TOK_EOF)
+      break;
+
+    /* --- Pick out the chunk name --- */
+
+    if (t != TOK_WORD) {
+      moan("expected <word>; found `%s'", decode(t));
+      goto done;
+    }
+
+    if (STRCMP(tok.buf, ==, "SUITE")) {
+      t = gettok(fp);
+      if (t != TOK_WORD) {
+       moan("expected <word>; found `%s'", decode(t));
+       goto done;
+      }
+      for (ss = suites; ; ss++) {
+       if (!ss->name) {
+         chunks = 0;
+         break;
+       }
+       if (STRCMP(tok.buf, ==, ss->name)) {
+         chunks = ss->chunks;
+         break;
+       }
+      }
+      continue;
+    }
+
+    /* --- Find the right chunk block --- */
+
+    if (!chunks)
+      goto skip_chunk;
+    for (cch = chunks; ; cch++) {
+      if (!cch->name)
+       goto skip_chunk;
+      if (STRCMP(tok.buf, ==, cch->name))
+       break;
+    }
+
+    /* --- Past the open brace to the first chunk --- */
+
+    if ((t = gettok(fp)) != '{') {
+      moan("expected `{'; found `%s'", decode(t));
+      goto done;
+    }
+
+    /* --- Start on the test data now --- */
+
+    printf("%s: ", cch->name);
+    fflush(stdout);
+    ok = 1;
+
+    for (;;) {
+      t = gettok(fp);
+
+      /* --- Accept a close brace --- */
+
+      if (t == '}')
+       break;
+
+      /* --- Otherwise I expect a list of words --- */
+
+      for (i = 0; cch->f[i]; i++) {
+       DRESET(&dv[i]);
+       if (t != TOK_WORD) {
+         moan("expected <word>; found `%s'", decode(t));
+         goto done;
+       }
+       cch->f[i]->cvt(tok.buf, &dv[i]);
+       t = gettok(fp);
+      }
+
+      /* --- And a terminating semicolon --- */
+
+      if (t != ';') {
+       moan("expected `;'; found `%s'", decode(t));
+       goto done;
+      }
+
+      /* --- Run the test code --- */
+
+      if (!cch->test(dv)) {
+       ok = 0;
+       printf("%s: ", cch->name);
+       for (i = 0; i < results->tests; i++) putchar('.');
+       results->failed++;
+      }
+      putchar('.');
+      results->tests++;
+      fflush(stdout);
+    }
+
+    puts(ok ? " ok" : " failed");
+    fflush(stdout);
+    continue;
+
+  skip_chunk:
+    if ((t = gettok(fp)) != '{') {
+      moan("expected '{'; found `%s'", decode(t));
+      goto done;
+    }
+    for (;;) {
+      t = gettok(fp);
+      if (t == '}')
+       break;
+      while (t == TOK_WORD)
+       t = gettok(fp);
+      if (t != ';') {
+       moan("expected `;'; found `%s'", decode(t));
+       goto done;
+      }
+    }
+  }
+  rc = results->failed;
+
+  /* --- All done --- */
+
+done:
+  for (i = 0; i < TEST_FIELDMAX; i++)
+    dstr_destroy(&dv[i]);
+  return (rc);
+}
+
+/* --- @test_run@ --- *
+ *
+ * Arguments:  @int argc@ = number of command line arguments
+ *             @char *argv[]@ = pointer to command line arguments
+ *             @const test_chunk chunk[]@ = pointer to chunk definitions
+ *             @const char *vec@ = name of default test vector file
+ *
+ * Returns:    Doesn't.
+ *
+ * Use:                Runs a set of test vectors to ensure that a component is
+ *             working properly.
+ */
+
+void test_run(int argc, char *argv[],
+             const test_chunk chunk[],
+             const char *vec)
+{
+  FILE *fp;
+  test_results res;
+  int rc;
+  test_suite suite[2];
+
+  /* --- Silly bits of initialization --- */
+
+  ego(argv[0]);
+
+  /* --- Parse command line arguments --- */
+
+  {
+    const char *p = 0;
+    int i = 0;
+
+    for (;;) {
+      if (!p || !*p) {
+       if (i >= argc - 1)
+         break;
+       p = argv[++i];
+       if (STRCMP(p, ==, "--")) {
+         i++;
+         break;
+       }
+       if (p[0] != '-' || p[1] == 0)
+         break;
+       p++;
+      }
+      switch (*p++) {
+       case 'h':
+         printf("%s test driver\n"
+                "Usage: %s [-f FILENAME]\n", QUIS, QUIS);
+         exit(0);
+       case 'f':
+         if (!*p) {
+           if (i >= argc - 1)
+             die(1, "option `-f' expects an argument");
+           p = argv[++i];
+         }
+         vec = p;
+         p = 0;
+         break;
+       default:
+         die(1, "option `-%c' unknown", p[-1]);
+         break;
+      }
+    }
+  }
+
+  /* --- Start parsing from the file --- */
+
+  if ((fp = fopen(vec, "r")) == 0)
+    die(1, "couldn't open test vector file `%s': %s", vec, strerror(errno));
+  suite[0].name = "simple";
+  suite[0].chunks = chunk;
+  suite[1].name = 0;
+  rc = test_do(suite, fp, &res);
+  if (rc < 0)
+    exit(127);
+  if (res.failed) {
+    fprintf(stderr, "FAILED %u of %u test%s\n",
+           res.failed, res.tests, res.tests == 1 ? "" : "s");
+  } else {
+    fprintf(stderr, "PASSED all %u test%s\n",
+           res.tests, res.tests == 1 ? "" : "s");
+  }
+  exit(!!res.failed);
+}
+
+/*----- That's all, folks -------------------------------------------------*/
diff --git a/test/testrig.h b/test/testrig.h
new file mode 100644 (file)
index 0000000..b37aa15
--- /dev/null
@@ -0,0 +1,133 @@
+/* -*-c-*-
+ *
+ * Generic test driver
+ *
+ * (c) 1998 Straylight/Edgeware
+ */
+
+/*----- Licensing notice --------------------------------------------------*
+ *
+ * This file is part of the mLib utilities library.
+ *
+ * mLib is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * mLib is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with mLib; if not, write to the Free
+ * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+ * MA 02111-1307, USA.
+ */
+
+#ifndef MLIB_TESTER_H
+#define MLIB_TESTER_H
+
+#ifdef __cplusplus
+  extern "C" {
+#endif
+
+/*----- Header files ------------------------------------------------------*/
+
+#include <stddef.h>
+
+#ifndef MLIB_BITS_H
+#  include "bits.h"
+#endif
+
+#ifndef MLIB_DSTR_H
+#  include "dstr.h"
+#endif
+
+#ifndef MLIB_MACROS_H
+#  include "macros.h"
+#endif
+
+/*----- Magical numbers ---------------------------------------------------*/
+
+#define TEST_FIELDMAX 16               /* Maximum fields in a line */
+
+/*----- Data structures ---------------------------------------------------*/
+
+typedef struct test_results {
+  unsigned tests, failed;
+} test_results;
+
+/* --- Test field definition --- */
+
+typedef struct test_type {
+  void (*cvt)(const char *buf, dstr *d); /* Conversion function */
+  void (*dump)(dstr *d, FILE *fp);     /* Dump function */
+} test_type;
+
+/* --- Test chunk definition --- */
+
+typedef struct test_chunk {
+  const char *name;                    /* Name of this chunk */
+  int (*test)(dstr /*dv*/[]);          /* Test verification function */
+  const test_type *f[TEST_FIELDMAX];   /* Field definitions */
+} test_chunk;
+
+typedef struct test_suite {
+  const char *name;                    /* Name of this suite */
+  const test_chunk *chunks;            /* Chunks contained in this suite */
+} test_suite;
+
+/*----- Predefined data types ---------------------------------------------*/
+
+extern const test_type type_hex;
+extern const test_type type_string;
+extern const test_type type_int;
+extern const test_type type_long;
+extern const test_type type_ulong;
+extern const test_type type_uint32;
+
+/*----- Functions provided ------------------------------------------------*/
+
+/* --- @test_do@ --- *
+ *
+ * Arguments:  @const test_suite suites[]@ = pointer to suite definitions
+ *             @FILE *fp@ = test vector file, ready opened
+ *             @test_results *results@ = where to put results
+ *
+ * Returns:    Negative if something bad happened, or the number of
+ *             failures.
+ *
+ * Use:                Runs a collection of tests against a file of test vectors and
+ *             reports the results.
+ */
+
+extern int test_do(const test_suite /*suite*/[],
+                  FILE */*fp*/,
+                  test_results */*results*/);
+
+/* --- @test_run@ --- *
+ *
+ * Arguments:  @int argc@ = number of command line arguments
+ *             @char *argv[]@ = pointer to command line arguments
+ *             @const test_chunk chunk[]@ = pointer to chunk definitions
+ *             @const char *def@ = name of default test vector file
+ *
+ * Returns:    Doesn't.
+ *
+ * Use:                Runs a set of test vectors to ensure that a component is
+ *             working properly.
+ */
+
+extern void NORETURN
+  test_run(int /*argc*/, char */*argv*/[],
+          const test_chunk /*chunk*/[],
+          const char */*def*/);
+
+/*----- That's all, folks -------------------------------------------------*/
+
+#ifdef __cplusplus
+  }
+#endif
+
+#endif
diff --git a/trace/Makefile.am b/trace/Makefile.am
new file mode 100644 (file)
index 0000000..45d362f
--- /dev/null
@@ -0,0 +1,40 @@
+### -*-makefile-*-
+###
+### Build script for tracing
+###
+### (c) 2009 Straylight/Edgeware
+###
+
+###----- Licensing notice ---------------------------------------------------
+###
+### This file is part of the mLib utilities library.
+###
+### mLib is free software; you can redistribute it and/or modify
+### it under the terms of the GNU Library General Public License as
+### published by the Free Software Foundation; either version 2 of the
+### License, or (at your option) any later version.
+###
+### mLib is distributed in the hope that it will be useful,
+### but WITHOUT ANY WARRANTY; without even the implied warranty of
+### MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+### GNU Library General Public License for more details.
+###
+### You should have received a copy of the GNU Library General Public
+### License along with mLib; if not, write to the Free
+### Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+### MA 02111-1307, USA.
+
+include $(top_srcdir)/vars.am
+
+noinst_LTLIBRARIES      = libtrace.la
+libtrace_la_SOURCES     =
+
+###--------------------------------------------------------------------------
+### Component files.
+
+## Tracing.
+pkginclude_HEADERS     += trace.h
+libtrace_la_SOURCES    += trace.c traceopt.c
+LIBMANS                        += trace.3
+
+###----- That's all, folks --------------------------------------------------
diff --git a/trace/trace.3 b/trace/trace.3
new file mode 100644 (file)
index 0000000..0a708bd
--- /dev/null
@@ -0,0 +1,171 @@
+.\" -*-nroff-*-
+.TH trace 3 "21 October 1999" "Straylight/Edgeware" "mLib utilities library"
+.SH "NAME"
+trace \- configurable tracing output
+.\" @trace
+.\" @trace_block
+.\" @trace_on
+.\" @trace_custom
+.\" @trace_level
+.\" @tracing
+.\" @traceopt
+.\" @NTRACE
+.\" @T
+.\" IF_TRACING
+.SH "SYNOPSIS"
+.nf
+.B "#include <mLib/trace.h>"
+
+.BI "void trace(unsigned " l ", const char *" f ", ...);"
+.BI "void trace_block(unsigned " l ", const char *" s ,
+.BI "                 const void *" b ", size_t " sz );
+
+.BI "void trace_on(FILE *" fp ", unsigned " l );
+.BI "void trace_custom(void (*" func ")(const char *" buf ,
+.BI "                               size_t " sz ", void *" v ),
+.BI "                  void *" v );
+.BI "void trace_level(unsigned " l );
+.BI "unsigned tracing(void);"
+
+.BI "unsigned traceopt(const trace_opt *" t ", const char *" p ,
+.BI "                  unsigned " f ", unsigned " bad );
+
+.BI T( statements\fR... )
+.BI "IF_TRACING(unsigned " l ", " statements\fR... )
+.fi
+.SH "DESCRIPTION"
+The
+.B <mLib/trace.h>
+header declares some functions and macros for handling trace output.
+The emphasis for this system is primarily on user configurability of
+what gets traced rather than where the output goes.  That's a project
+for later.
+.SS "Trace levels"
+Each trace message is assigned a
+.I level
+by the programmer.  A tracing level is set during program
+initialization, usually by the user.  A trace message is output if there
+is a trace destination set, and the result of a bitwise AND between the
+message level and the overall tracing level is nonzero.  The idea is
+that the programmer assigns a different bit to each group of trace
+messages, and allows a user to select which ones are wanted.
+.SS "Producing trace output"
+The basic function is
+.BR trace .
+It is passed an integer message level and a
+.BR printf (3)-style
+format string together with arguments for the placeholders and emits the
+resulting message.
+.PP
+The function
+.B trace_block
+formats an arbitrary block of memory as a hex dump.  The arguments are,
+in order, the message level, a pointer to the header string for the hex
+dump, the base address of the block to dump, and the size of the block
+in bytes.
+.SS "Configuring trace output"
+The tracing destination is set with the function
+.BR trace_on :
+it is passed a
+.B stdio
+stream and a trace level to set.  The stream may be null to disable
+tracing completely (which is the default).  The trace level may be set
+on its own using
+.BR trace_level ,
+which takes the new level as its single argument.  The function
+.B tracing
+returns the current trace level, or zero if there is no trace
+destination set.
+.PP
+For more advanced programs, a custom output function may be provided by
+.B trace_custom
+and passing a function which will write a buffer of data somewhere
+sensible.
+.SS "Parsing user trace options"
+The function
+.B traceopt
+may be used to allow a user to set the trace level.  It is passed a
+table describing the available trace level bits, and some other
+information, and returns a new trace level.  The table consists of a
+number of
+.B trace_opt
+structures, each of which describes a bit or selection of bits which may
+be controlled.  The structure contains the following members, in order:
+.TP
+.B "char ch;"
+The character used to select this bit or collection of bits.
+.TP
+.B "unsigned f;"
+The level bits for this option.
+.TP
+.B "const char *help;"
+A help string describing this option.
+.PP
+The table is terminated by an entry whose
+.B ch
+member is zero.
+.PP
+The arguments to
+.B traceopt
+are:
+.TP
+.BI "trace_opt *" t
+Pointer to the trace options table.
+.TP
+.BI "const char *" p
+Pointer to the user's option string.
+.TP
+.BI "unsigned " f
+The default trace options, or the previously-set options.
+.TP
+.BI "unsigned " bad
+A bitmask of level bits which should be disallowed.
+.PP
+If the option string
+.I p
+is a null pointer or contains only a
+.RB ` ? '
+character, a help message is printed and the default is returned.  Only
+trace options which contain non-bad bits are displayed.  Otherwise the
+string contains option characters together with
+.RB ` + '
+and
+.RB ` \- '
+which respectively turn on or off the following options; the default is
+to turn options on.  Again, only options which contain non-bad bits are
+allowed.
+.PP
+The `bad bit' mechanism is provided for setuid programs which in their
+normal configuration deal with privileged information which mustn't be
+given out to users.  However, if run by someone with appropriate
+privilege such options are safe and may be useful for debugging.  The
+program can set the
+.I bad
+mask to prevent access to secret information when running setuid, or to
+zero when not.
+.SS "Macro support for tracing"
+The tracing macros are intended to make insertion of tracing information
+unobtrusive and painless.  If the
+.B NTRACE
+macro is defined, all of the tracing macros are disabled and generate no
+code; otherwise they do their normal jobs.
+.PP
+The
+.B T
+macro simply expands to its argument.  It may be wrapped around small
+pieces of code which is only needed when compiling with tracing
+enabled.  (Larger blocks, of course, should use
+.RB ` "#ifndef NTRACE" '/` #endif '
+pairs for clarity's sake.)
+.PP
+For slightly larger code chunks which do some processing to generate
+trace output, the
+.B IF_TRACING
+macro is useful.  Its first argument is a message level; if the trace
+level is set such that the message will be printed, the code in the
+second argument is executed.  If code is being compiled without tracing,
+of course, the entire contents of the macro is ignored.
+.SH "SEE ALSO"
+.BR mLib (3).
+.SH "AUTHOR"
+Mark Wooding, <mdw@distorted.org.uk>
diff --git a/trace/trace.c b/trace/trace.c
new file mode 100644 (file)
index 0000000..e06bf7b
--- /dev/null
@@ -0,0 +1,213 @@
+/* -*-c-*-
+ *
+ * Tracing functions for debugging
+ *
+ * (c) 1998 Straylight/Edgeware
+ */
+
+/*----- Licensing notice --------------------------------------------------*
+ *
+ * This file is part of the mLib utilities library.
+ *
+ * mLib is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * mLib is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with mLib; if not, write to the Free
+ * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+ * MA 02111-1307, USA.
+ */
+
+/*----- Header files ------------------------------------------------------*/
+
+/* --- ANSI headers --- */
+
+#include <ctype.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+/* --- Local headers --- */
+
+#include "dstr.h"
+#include "macros.h"
+#include "quis.h"
+#include "trace.h"
+
+/*----- Private state information -----------------------------------------*/
+
+static void (*tracefunc)(const char *buf, size_t sz, void *v) = 0;
+static void *tracearg;
+static unsigned tracelvl = 0;          /* How much tracing gets done? */
+
+/*----- Functions provided ------------------------------------------------*/
+
+/* --- @t_file@ --- *
+ *
+ * Arguments:  @const char *buf@ = buffer to print
+ *             @size_t sz@ = buffer size
+ *             @void *v@ = file handle
+ *
+ * Returns:    ---
+ *
+ * Use:                Dumps tracing information to a file.
+ */
+
+static void t_file(const char *buf, size_t sz, void *v)
+{
+  FILE *fp = v;
+  fprintf(fp, "+ %s: ", QUIS);
+  fwrite(buf, 1, sz, fp);
+  fputc('\n', fp);
+}
+
+/* --- @trace@ --- *
+ *
+ * Arguments:  @unsigned l@ = trace level for output
+ *             @const char *f@ = a @printf@-style format string
+ *             @...@ = other arguments
+ *
+ * Returns:    ---
+ *
+ * Use:                Reports a message to the trace output.
+ */
+
+void trace(unsigned l, const char *f, ...)
+{
+  va_list ap;
+  dstr d = DSTR_INIT;
+  if ((l & tracing()) == 0)
+    return;
+  va_start(ap, f);
+  dstr_vputf(&d, f, &ap);
+  va_end(ap);
+  tracefunc(d.buf, d.len, tracearg);
+  dstr_destroy(&d);
+}
+
+/* --- @trace_block@ --- *
+ *
+ * Arguments:  @unsigned l@ = trace level for output
+ *             @const char *s@ = some header string to write
+ *             @const void *b@ = pointer to a block of memory to dump
+ *             @size_t sz@ = size of the block of memory
+ *
+ * Returns:    ---
+ *
+ * Use:                Dumps the contents of a block to the trace output.
+ */
+
+void trace_block(unsigned l, const char *s, const void *b, size_t sz)
+{
+  const unsigned char *p = b;
+  size_t i;
+  unsigned long o = 0;
+  dstr d = DSTR_INIT;
+  size_t c;
+
+  /* --- Skip if the trace level is too high --- */
+
+  if ((l & tracing()) == 0)
+    return;
+
+  /* --- Now start work --- */
+
+  tracefunc(s, strlen(s), tracearg);
+  while (sz) {
+    dstr_reset(&d);
+    dstr_putf(&d, "   %08lx : ", o);
+    for (i = 0; i < 8; i++) {
+      if (i < sz)
+       dstr_putf(&d, "%02x ", p[i]);
+      else
+       dstr_puts(&d, "** ");
+    }
+    dstr_puts(&d, ": ");
+    for (i = 0; i < 8; i++) {
+      if (i < sz)
+       dstr_putc(&d, ISPRINT(p[i]) ? p[i] : '.');
+      else
+       dstr_putc(&d, '*');
+    }
+    dstr_putz(&d);
+    tracefunc(d.buf, d.len, tracearg);
+    c = (sz >= 8) ? 8 : sz;
+    sz -= c, p += c, o += c;
+  }
+  dstr_destroy(&d);
+}
+
+/* --- @trace_on@ --- *
+ *
+ * Arguments:  @FILE *fp@ = a file to trace on
+ *             @unsigned l@ = trace level to set
+ *
+ * Returns:    ---
+ *
+ * Use:                Enables tracing to a file.
+ */
+
+void trace_on(FILE *fp, unsigned l)
+{
+  tracefunc = t_file;
+  tracearg = fp;
+  if (!tracelvl)
+    tracelvl = l;
+}
+
+/* --- @trace_custom@ --- *
+ *
+ * Arguments:  @void (*func)(const char *buf, size_t sz, void *v)@ =
+ *                     output function
+ *             @void *v@ = magic handle to give to function
+ *
+ * Returns:    ---
+ *
+ * Use:                Sets up a custom trace handler.
+ */
+
+void trace_custom(void (*func)(const char */*buf*/,
+                              size_t /*sz*/, void */*v*/),
+                 void *v)
+{
+  tracefunc = func;
+  tracearg = v;
+}
+
+/* --- @trace_level@ --- *
+ *
+ * Arguments:  @unsigned l@ = trace level to set
+ *
+ * Returns:    ---
+ *
+ * Use:                Sets the tracing level.
+ */
+
+void trace_level(unsigned l)
+{
+  tracelvl = l;
+}
+
+/* --- @tracing@ --- *
+ *
+ * Arguments:  ---
+ *
+ * Returns:    Zero if not tracing, tracing level if tracing.
+ *
+ * Use:                Informs the caller whether tracing is enabled.
+ */
+
+unsigned tracing(void)
+{
+  return (tracefunc ? tracelvl : 0u);
+}
+
+/*----- That's all, folks -------------------------------------------------*/
diff --git a/trace/trace.h b/trace/trace.h
new file mode 100644 (file)
index 0000000..a7edc37
--- /dev/null
@@ -0,0 +1,164 @@
+/* -*-c-*-
+ *
+ * Tracing functions for debugging
+ *
+ * (c) 1998 Straylight/Edgeware
+ */
+
+/*----- Licensing notice --------------------------------------------------*
+ *
+ * This file is part of the mLib utilities library.
+ *
+ * mLib is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * mLib is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with mLib; if not, write to the Free
+ * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+ * MA 02111-1307, USA.
+ */
+
+#ifndef MLIB_TRACE_H
+#define MLIB_TRACE_H
+
+#ifdef __cplusplus
+  extern "C" {
+#endif
+
+/*----- Header files ------------------------------------------------------*/
+
+#include <stdio.h>
+
+#ifndef MLIB_MACROS_H
+#  include "macros.h"
+#endif
+
+/*----- Data structures ---------------------------------------------------*/
+
+typedef struct trace_opt {
+  char ch;
+  unsigned f;
+  const char *help;
+} trace_opt;
+
+/*----- Functions provided ------------------------------------------------*/
+
+/* --- @trace@ --- *
+ *
+ * Arguments:  @unsigned l@ = trace level for output
+ *             @const char *f@ = a @printf@-style format string
+ *             @...@ = other arguments
+ *
+ * Returns:    ---
+ *
+ * Use:                Reports a message to the trace output.
+ */
+
+extern void PRINTF_LIKE(2, 3)
+  trace(unsigned /*l*/, const char */*f*/, ...);
+
+/* --- @trace_block@ --- *
+ *
+ * Arguments:  @unsigned l@ = trace level for output
+ *             @const char *s@ = some header string to write
+ *             @const void *b@ = pointer to a block of memory to dump
+ *             @size_t sz@ = size of the block of memory
+ *
+ * Returns:    ---
+ *
+ * Use:                Dumps the contents of a block to the trace output.
+ */
+
+extern void trace_block(unsigned /*l*/, const char */*s*/,
+                       const void */*b*/, size_t /*sz*/);
+
+/* --- @trace_on@ --- *
+ *
+ * Arguments:  @FILE *fp@ = a file to trace on
+ *             @unsigned l@ = trace level to set
+ *
+ * Returns:    ---
+ *
+ * Use:                Enables tracing to a file.
+ */
+
+extern void trace_on(FILE */*fp*/, unsigned /*l*/);
+
+/* --- @trace_custom@ --- *
+ *
+ * Arguments:  @void (*func)(const char *buf, size_t sz, void *v)@ =
+ *                     output function
+ *             @void *v@ = magic handle to give to function
+ *
+ * Returns:    ---
+ *
+ * Use:                Sets up a custom trace handler.
+ */
+
+extern void trace_custom(void (*/*func*/)(const char */*buf*/,
+                                         size_t /*sz*/, void */*v*/),
+                        void */*v*/);
+
+/* --- @trace_level@ --- *
+ *
+ * Arguments:  @unsigned l@ = trace level to set
+ *
+ * Returns:    ---
+ *
+ * Use:                Sets the tracing level.
+ */
+
+extern void trace_level(unsigned /*l*/);
+
+/* --- @tracing@ --- *
+ *
+ * Arguments:  ---
+ *
+ * Returns:    Zero if not tracing, tracing level if tracing.
+ *
+ * Use:                Informs the caller whether tracing is enabled.
+ */
+
+extern unsigned tracing(void);
+
+/* --- @traceopt@ --- *
+ *
+ * Arguments:  @const trace_opt *t@ = pointer to trace options table
+ *             @const char *p@ = option string supplied by user
+ *             @unsigned f@ = initial tracing flags
+ *             @unsigned bad@ = forbidden tracing flags
+ *
+ * Returns:    Trace flags as set by user.
+ *
+ * Use:                Parses an option string from the user and sets the
+ *             appropriate trace flags.  If the argument is null or a single
+ *             `?' character, a help message is displayed.
+ */
+
+extern unsigned traceopt(const trace_opt */*t*/, const char */*p*/,
+                        unsigned /*f*/, unsigned /*bad*/);
+
+/*----- Tracing macros ----------------------------------------------------*/
+
+#ifndef NTRACE
+#  define T(x) x
+#  define IF_TRACING(l, x) if ((l) & tracing()) x
+#else
+#  define T(x)
+#  define IF_TRACING(l, x)
+#endif
+
+/*----- That's all, folks -------------------------------------------------*/
+
+#ifdef __cplusplus
+  }
+#endif
+
+#endif
diff --git a/trace/traceopt.c b/trace/traceopt.c
new file mode 100644 (file)
index 0000000..bbb8b02
--- /dev/null
@@ -0,0 +1,106 @@
+/* -*-c-*-
+ *
+ * Parsing tracing options
+ *
+ * (c) 1999 Straylight/Edgeware
+ */
+
+/*----- Licensing notice --------------------------------------------------*
+ *
+ * This file is part of the mLib utilities library.
+ *
+ * mLib is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * mLib is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with mLib; if not, write to the Free
+ * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+ * MA 02111-1307, USA.
+ */
+
+/*----- Header files ------------------------------------------------------*/
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+
+#include "macros.h"
+#include "report.h"
+#include "trace.h"
+
+/*----- Main code ---------------------------------------------------------*/
+
+/* --- @traceopt@ --- *
+ *
+ * Arguments:  @const trace_opt *t@ = pointer to trace options table
+ *             @const char *p@ = option string supplied by user
+ *             @unsigned f@ = initial tracing flags
+ *             @unsigned bad@ = forbidden tracing flags
+ *
+ * Returns:    Trace flags as set by user.
+ *
+ * Use:                Parses an option string from the user and sets the
+ *             appropriate trace flags.  If the argument is null or a single
+ *             `?' character, a help message is displayed.
+ */
+
+unsigned traceopt(const trace_opt *t, const char *p,
+                 unsigned f, unsigned bad)
+{
+  unsigned sense = 1;
+
+  /* --- Dump out help text --- */
+
+  if (!p || STRCMP(p, ==, "?")) {
+    const trace_opt *tt;
+    puts("Trace options:");
+    for (tt = t; tt->ch; tt++) {
+      if (!(tt->f & ~bad) || !tt->help)
+       continue;
+      printf(" `%c': %s\n", tt->ch, tt->help);
+    }
+    return (f);
+  }
+
+  /* --- Parse the string properly --- */
+
+  f = 0;
+  while (*p) {
+    switch (*p) {
+      case '+':
+       sense = 1;
+       break;
+      case '-':
+       sense = 0;
+       break;
+      default: {
+       const trace_opt *tt;
+       for (tt = t; tt->ch; tt++) {
+         if (!(tt->f & ~bad))
+           continue;
+         if (tt->ch == *p) {
+           if (sense)
+             f |= (tt->f & ~bad);
+           else
+             f &= ~(tt->f & ~bad);
+           goto ok;
+         }
+       }
+       moan("unknown trace option `%c'", *p);
+      ok:;
+      } break;
+    }
+    p++;
+  }
+
+  return (f);
+}
+
+/*----- That's all, folks -------------------------------------------------*/
diff --git a/ui/Makefile.am b/ui/Makefile.am
new file mode 100644 (file)
index 0000000..ab70a70
--- /dev/null
@@ -0,0 +1,54 @@
+### -*-makefile-*-
+###
+### Build script for user interface
+###
+### (c) 2009 Straylight/Edgeware
+###
+
+###----- Licensing notice ---------------------------------------------------
+###
+### This file is part of the mLib utilities library.
+###
+### mLib is free software; you can redistribute it and/or modify
+### it under the terms of the GNU Library General Public License as
+### published by the Free Software Foundation; either version 2 of the
+### License, or (at your option) any later version.
+###
+### mLib is distributed in the hope that it will be useful,
+### but WITHOUT ANY WARRANTY; without even the implied warranty of
+### MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+### GNU Library General Public License for more details.
+###
+### You should have received a copy of the GNU Library General Public
+### License along with mLib; if not, write to the Free
+### Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+### MA 02111-1307, USA.
+
+include $(top_srcdir)/vars.am
+
+noinst_LTLIBRARIES      = libui.la
+libui_la_SOURCES        =
+libui_la_LIBADD                 =
+
+###--------------------------------------------------------------------------
+### Component files.
+
+## Option parsing.
+noinst_LTLIBRARIES     += libmdwopt.la
+pkginclude_HEADERS     += mdwopt.h
+libmdwopt_la_SOURCES    = mdwopt.c
+libmdwopt_la_CPPFLAGS   = $(AM_CPPFLAGS) -DBUILDING_MLIB
+libui_la_LIBADD                += libmdwopt.la
+LIBMANS                        += mdwopt.3
+
+## Program naming.
+pkginclude_HEADERS     += quis.h
+libui_la_SOURCES       += quis.c pquis.c
+LIBMANS                        += quis.3
+
+## Error reporting.
+pkginclude_HEADERS     += report.h
+libui_la_SOURCES       += report.c
+LIBMANS                        += report.3
+
+###----- That's all, folks --------------------------------------------------
diff --git a/ui/mdwopt.3 b/ui/mdwopt.3
new file mode 100644 (file)
index 0000000..11111aa
--- /dev/null
@@ -0,0 +1,433 @@
+.\" -*-nroff-*-
+.TH mdwopt 3 "6 July 1999" "Straylight/Edgeware" "mLib utilities library"
+.SH "NAME"
+mdwopt \- command-line option parser
+.\" @mdwopt
+.SH "SYNOPSIS"
+.nf
+.B "#include <mLib/mdwopt.h>"
+
+.BI "int mdwopt(int " argc ", char *const *" argv ,
+.BI "           const char *" shortopt ,
+.BI "           const struct option *" longopt ", int *" longind ,
+.BI "           mdwopt_data *" data ", int " flags );
+
+.BI "int getopt(int " argc ", char *const *" argv ", const char *" o );
+
+.BI "int getopt_long(int " argc ", char *const *" argv ,
+.BI "                const char * "shortopt ,
+.BI "                const struct option *" longopt ", int *" longind );
+
+.BI "int getopt_long_only(int " argc ", char *const *" argv ,
+.BI "                    const char * "shortopt ,
+.BI "                    const struct option *" longopt ", int *" longind );
+.fi
+.SH "OVERVIEW"
+The
+.B mdwopt
+function is a command line options parser which is (mostly) compatible
+with the standard POSIX and GNU
+.B getopt
+functions, although provides more features than either.  It's not the
+most featureful options parser around, but it's good enough for my
+purposes at the moment.
+.SH "OPTION SYNTAX"
+A command line consists of a number of
+.I words
+(which may contain spaces, according to various shell quoting
+conventions).  A word may be an option, an argument to an option, or a
+non-option.  An option begins with a special character, usually
+.RB ` \- ',
+although
+.RB ` + '
+is also used sometimes.  As special exceptions, the word containing only
+a
+.RB ` \- '
+is considered to be a non-option, since it usually represents standard
+input or output as a filename, and the word containing only a
+double-dash
+.RB ` \-\- '
+is used to mark all following words as being non-options regardless of
+their initial character.
+.PP
+Traditionally, all words after the first non-option have been considered
+to be non-options automatically, so that options must be specified
+before filenames.  However, this implementation can extract all the
+options from the command line regardless of their position.  This can
+usually be disabled by setting one of the environment variables
+.B POSIXLY_CORRECT
+or
+.BR _POSIX_OPTION_ORDER .
+.PP
+There are two different styles of options:
+.I short
+and
+.IR long .
+Traditional Unix (and POSIX) only uses short options.  The long options
+are a GNU convention.
+.SS "Short option syntax"
+Short options are the sort which Unix has known for ages: an option is a
+single letter, preceded by a
+.RB ` \- '.
+Short options can be joined together to save space (and possibly to make
+silly words): e.g., instead of giving options
+.RB ` "\-x \-y" ',
+a user could write
+.RB ` \-xy '.
+Some short options can have arguments which appear after the option
+letter, either immediately following, or in the next word; so an option
+with an argument could be written as
+.RB ` "\-o foo" '
+or as
+.RB ` \-ofoo ').
+Note that options with optional arguments must be written in the second
+style.
+.PP
+When a short option controls a flag setting, it is sometimes possible to
+explicitly turn the flag off, as well as turning it on, (usually to
+override default options).  This is usually done by using a
+.RB ` + '
+instead of a
+.RB ` \- '
+to introduce the option.  (Some programs use upper-case option letters
+to indicate this instead.)
+.SS "Long option syntax"
+Long options, as popularized by the GNU utilities, are given long-ish
+memorable names, preceded by a double-dash
+.RB ` \-\- '.
+Since their names are more than a single character, long options can't
+be combined in the same way as short options.  Arguments to long options
+may be given either in the same word, separated from the option name by
+an equals sign, or in the following word.
+.PP
+Long option names can be abbreviated if necessary, as long as the
+abbreviation is unique.  This means that options can have sensible and
+memorable names but still not require much typing from an experienced
+user.
+.PP
+Like short options, long options can control flag settings.  The options
+to manipulate these settings come in pairs: an option of the form
+.RB ` \-\-set\-flag '
+might set the flag, while an option of the form
+.RB ` \-\-no\-set\-flag '
+might clear it.
+.PP
+It is usual for applications to provide both short and long options with
+identical behaviour.  Some applications with lots of options may only
+provide long options (although they will often be only two or three
+characters long).  In this case, long options can be preceded with a
+single
+.RB ` \- '
+character, and negated by a
+.RB ` + '
+character.
+.SS "Numerical options"
+Finally, some (older) programs accept arguments of the form
+.RB ` \- \c
+.IR number ',
+to set some numerical parameter, typically a line count of some kind.
+.SH "PARSING OPTIONS WITH \fBmdwopt\fP"
+An application parses its options by calling
+.B mdwopt
+repeatedly.  Each time it is called,
+.B mdwopt
+returns a value describing the option just read, and stores information
+about the option in a data block.
+.PP
+The data block is a structure containing at least the following members:
+.TP
+.B "char *arg"
+Pointer to the argument of the current option, or null.  Argument
+strings persist for as long as the underlying command line argument
+array
+.I argv
+does, so it's usually safe just to remember the pointer.
+.TP
+.B "int opt"
+Value of the current option
+.TP
+.B "int ind"
+Must be initialized to 0 before the first call to
+.BR mdwopt .
+After the last call, it is the index into
+.I argv
+of the first nonoption argument.
+.TP
+.B "int err"
+Set to nonzero to allow
+.B mdwopt
+to emit error messages about illegal option syntax.  (This would be a
+flag setting, but it has to be here for
+.B getopt
+compatibility.)
+.TP
+.B "char *prog"
+Contains the program's name, stripped of any path prefix.  This is an
+obsolete feature: the
+.BR quis (3)
+module does the job in a more sensible way.
+.PP
+Prior to the first call to
+.BR mdwopt ,
+the
+.B err
+and
+.B ind
+members of the structure must be initialized.
+.PP
+The arguments
+.I argc
+and
+.I argv
+describe the command-line argument array which is to be parsed.  These
+will usually be exactly the arguments passed to the program's
+.B main
+function.
+.SS "Short option parsing"
+Short options are described by a string,
+.IR shortopt ,
+which once upon a time just contained the permitted option characters.
+Now the options string begins with a collection of flag characters, and
+various flag characters can be put after options characters to change
+their properties.
+.PP
+If the first character of the short options string is
+.RB ` + ',
+.RB ` \- '
+or
+.RB ` ! ',
+the order in which options are read is modified, as follows:
+.TP
+.RB ` + '
+Forces the POSIX order to be used. As soon as a non-option is found,
+.B mdwopt
+returns \-1.
+.TP
+.RB ` \- '
+Makes
+.B mdwopt
+treat non-options as being `special' sorts of option. When a non-option
+word is found, the value 0 is returned, and the actual text of the word
+is stored as being the option's argument.
+.TP
+.RB ` ! '
+forces the default order to be used regardless of environment variable
+settings.  The entire command line is scanned for options, which are
+returned in order.  However, during this process, the options are moved
+in the
+.I argv
+array, so that they appear before the non-options.
+.PP
+A
+.RB ` : '
+character may be placed after the ordering flag (or at the very
+beginning if no ordering flag is given) which indicates that the
+character
+.RB ` : ',
+rather than
+.RB ` ? ',
+should be returned if a missing argument error is detected.
+.PP
+Each option in the string can be followed by a
+.RB ` + '
+sign, indicating that it can be negated, a
+.RB ` : '
+sign indicating that it requires an argument, or a
+.RB ` :: '
+string, indicating an optional argument.  Both
+.RB ` + '
+and one of
+.RB ` : '
+or
+.RB ` :: '
+may be given, although the
+.RB ` + '
+must come first.
+.PP
+If an option is found, the option character is returned to the caller.
+A pointer to an argument is stored in the
+.B arg
+member of the data block; a null pointer is stored if there was no
+argument.  If a negated option was found, the option character is
+returned ORed with
+.B OPTF_NEGATED
+(bit 8 set).
+.SS "Long option parsing"
+Long options are described in a table.  Each entry in the
+table is of type
+.BR "struct option" ,
+which contains the following members (in order):
+.TP
+.B "const char *name"
+Pointer to the option's name.
+.TP
+.B "int has_arg"
+A flags word describing the option.  (The name is historical.)
+.TP
+.B "int *flag"
+Address of the flag variable to use when this option is matched.
+.TP
+.B "int val"
+Value to store or return when this option is matched.
+.PP
+The table is terminated by an entry whose
+.B name
+field is a null pointer.
+.PP
+When
+.B mdwopt
+finds a long option, it looks the name up in the table. The index of the
+matching entry is stored in the
+.I longind
+variable, passed to
+.B mdwopt
+(unless
+.I longind
+is null): a value of \-1 indicates that no long option was found. The
+behaviour is then dependent on the values in the table entry.
+.PP
+If the flag bit
+.B OPTF_ARGREQ
+is set in
+.B has_arg
+then the option has a required argument, which may be separated from the
+option name by an equals sign or placed in the following word.  If the
+flag bit
+.B OPTF_ARGOPT
+is set then the argument is optional.  If present, the argument must be
+in the same word as the option name, separated by an equals sign.  It is
+an error for both flags to be set; if neither is set then the option
+does not take an argument.
+.PP
+If
+.B flag
+is nonzero, it points to an integer to be modified by
+.BR mdwopt .
+Usually the value in the
+.B val
+field is simply stored in the
+.B flag
+variable. If the flag
+.B OPTF_SWITCH
+is set in the
+.B has_arg
+member, however, the value is combined with the existing value of the
+flags using a bitwise OR.  If
+.B OPTF_NEGATE
+is set in the
+.B has_arg
+field, then the flag bit will be cleared if a matching negated long
+option is found.  The value 0 is returned.
+.PP
+If
+.B flag
+is zero, the value in
+.B val
+is returned by
+.BR mdwopt ,
+possibly with bit 8 set if the option was
+negated.
+.PP
+Arguments from long options are stored in the
+.B arg
+member of the data block.
+.SS "Other optional features"
+The
+.I flags
+argument contains a bitmask of features which may be enabled:
+.TP
+.B OPTF_NOLONGS
+Don't allow any long options.  This makes
+.B mdwopt
+compatible with traditional Unix
+.BR getopt .
+.TP
+.B OPTF_NOSHORTS
+A slightly misnamed flag.  Short options are read normally.  However,
+long options may also begin with a single dash
+.RB ` \- '
+(or the
+.RB ` + '
+sign if negated).  Long options may not be combined with short options:
+an option word which begins with a short option must contain only short
+options.
+.TP
+.B OPTF_NUMBERS
+Read numeric options.  If a numeric option is found, the character
+.RB ` # '
+is returned and the text of the number is stored in the
+.B arg
+member of the data block.
+.TP
+.B OPTF_NEGATION
+Allow negation of options.  Negated options are returned ORed with
+.BR OPTF_NEGATED .
+.TP
+.B OPTF_ENVVAR
+Options will be read from an environment variable before scanning the
+actual command line provided.  The name of the environment variable is
+found by capitalizing the program name.  (This allows a user to have
+different default settings for a program, by calling it through
+different symbolic links.)
+.TP
+.B OPTF_NOPROGNAME
+Don't read the program name from
+.IR argv \c
+.BR [0] ,
+and don't set the
+.B prog
+data block member.  Options start right at the beginning of
+.IR argv .
+.TP
+.B OPTF_NEGNUMBER
+Allow negated numeric options.  Negated numeric options begin with a
+.RB ` + '
+rather than a
+.RB ` \- '.
+The return value is
+.RB ` # ' " | OPTF_NEGATED" .
+.SS "Compatibility features"
+The macros
+.BR getopt ,
+.B getopt_long
+and
+.B getopt_long_only
+correspond to calls to
+.B mdwopt
+with various flag settings.  See the macro definitions for the actual
+mappings, and the documentation for the functions to see how they're
+meant to work.
+.PP
+Additionally, there is a global data block, which is specified by
+passing a null
+.I data
+argument to
+.BR mdwopt .
+The members of this block may be referred to by their traditional names:
+.TP
+.B optarg
+The argument of the current option.
+.TP
+.B optopt
+Option code of the current option.
+.TP
+.B opterr
+Nonzero if
+.B mdwopt
+is to report errors.  This is the default.
+.TP
+.B optind
+Index of the first non-option argument.
+.TP
+.B optprog
+Name of the program, stripped of path prefix.
+.PP
+These names aren't considered deprecated: they help make the code easier
+to read by people used to the traditional
+.B getopt
+function.
+.SH "SEE ALSO"
+.BR getopt (3),
+.BR mLib (3).
+.SH "AUTHOR"
+Mark Wooding, <mdw@distorted.org.uk>
diff --git a/ui/pquis.c b/ui/pquis.c
new file mode 100644 (file)
index 0000000..00b738e
--- /dev/null
@@ -0,0 +1,76 @@
+/* -*-c-*-
+ *
+ * Print strings, substituting the program name
+ *
+ * (c) 1999 Straylight/Edgeware
+ */
+
+/*----- Licensing notice --------------------------------------------------*
+ *
+ * This file is part of the mLib utilities library.
+ *
+ * mLib is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * mLib is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with mLib; if not, write to the Free
+ * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+ * MA 02111-1307, USA.
+ */
+
+/*----- Header files ------------------------------------------------------*/
+
+#include <stdio.h>
+#include <string.h>
+
+#include "quis.h"
+
+/*----- Main code ---------------------------------------------------------*/
+
+/* --- @pquis@ --- *
+ *
+ * Arguments:  @FILE *fp@ = output stream to write on
+ *             @const char *p@ = pointer to string to write
+ *
+ * Returns:    Zero if everything worked, EOF if not.
+ *
+ * Use:                Writes the string @p@ to the output stream @fp@.  Occurrences
+ *             of the character `$' in @p@ are replaced by the program name
+ *             as reported by @quis@.  A `$$' is replaced by a single `$'
+ *             sign.
+ */
+
+int pquis(FILE *fp, const char *p)
+{
+  size_t sz;
+
+  while (*p) {
+    sz = strcspn(p, "$");
+    if (sz) {
+      if (fwrite(p, 1, sz, fp) < sz)
+       return (EOF);
+      p += sz;
+    }
+    if (*p == '$') {
+      p++;
+      if (*p == '$') {
+       if (fputc('$', fp) == EOF)
+         return (EOF);
+       p++;
+      } else {
+       if (fputs(pn__name, fp) == EOF)
+         return (EOF);
+      }
+    }
+  }
+  return (0);
+}
+
+/*----- That's all, folks -------------------------------------------------*/
diff --git a/ui/quis.3 b/ui/quis.3
new file mode 100644 (file)
index 0000000..bd19698
--- /dev/null
+++ b/ui/quis.3
@@ -0,0 +1,64 @@
+.\" -*-nroff-*-
+.TH quis 3 "22 May 1999" "Straylight/Edgeware" "mLib utilities library"
+.SH NAME
+quis \- remember the program's name for use in messages
+.\" @quis
+.\" @ego
+.\" @QUIS
+.\" @pquis
+.SH SYNOPSIS
+.nf
+.B "#include <mLib/quis.h>"
+
+.BI "void ego(const char *" p );
+.B "const char *quis(void);"
+.B "const char *QUIS;"
+.BI "int pquis(FILE *" fp ", const char *" p );
+.fi
+.SH DESCRIPTION
+The
+.B ego
+function should be called early in your program's initialization
+sequence, with the value of
+.B argv[0]
+as its argument.  It will strip away leading path components, and a
+leading `\-' character (in case the program was called as a login
+shell), and keep the resulting short name for later.
+.PP
+The
+.B quis
+function returns the stored program name.  There is also a macro
+.B QUIS
+which expands to the name of a global variable whose value is the string
+returned by
+.BR quis() .
+.PP
+Don't ask why it's done this way.  There are raisins, but they're mostly
+hysterical.
+.PP
+The function
+.B pquis
+is passed a file pointer
+.I fp
+and a string
+.IR p :
+it writes the string to the file, replacing every lone occurrence of the
+character
+.RB ` $ '
+by the program name.  Pairs
+.RB (` $$ ')
+are written as single dollar signs.  The return value is zero if
+everything went OK, or the constant
+.B EOF
+if there was an error.
+.PP
+The program name is used in the messages produced by the
+.BR die (3)
+and
+.BR moan (3)
+functions.
+.SH "SEE ALSO"
+.BR report (3),
+.BR mLib (3).
+.SH AUTHOR
+Mark Wooding, <mdw@distorted.org.uk>
diff --git a/ui/quis.c b/ui/quis.c
new file mode 100644 (file)
index 0000000..c3d189b
--- /dev/null
+++ b/ui/quis.c
@@ -0,0 +1,87 @@
+/* -*-c-*-
+ *
+ * Setting the program name
+ *
+ * (c) 1998 Straylight/Edgeware
+ */
+
+/*----- Licensing notice --------------------------------------------------*
+ *
+ * This file is part of the mLib utilities library.
+ *
+ * mLib is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * mLib is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with mLib; if not, write to the Free
+ * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+ * MA 02111-1307, USA.
+ */
+
+/*----- Header files ------------------------------------------------------*/
+
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "quis.h"
+
+/*----- Global variables --------------------------------------------------*/
+
+const char *pn__name = "<UNNAMED>";    /* Program name */
+
+/*----- Functions provided ------------------------------------------------*/
+
+/* --- @quis@ --- *
+ *
+ * Arguments:  ---
+ *
+ * Returns:    Pointer to the program name.
+ *
+ * Use:                Returns the program name.
+ */
+
+const char *quis(void) { return (QUIS); }
+
+/* --- @ego@ --- *
+ *
+ * Arguments:  @const char *p@ = pointer to program name
+ *
+ * Returns:    ---
+ *
+ * Use:                Tells mLib what the program's name is.
+ */
+
+#ifndef PATHSEP
+#  if defined(__riscos)
+#    define PATHSEP '.'
+#  elif defined(__unix) || defined(unix)
+#    define PATHSEP '/'
+#  else
+#    define PATHSEP '\\'
+#  endif
+#endif
+
+void ego(const char *p)
+{
+  const char *q = p;
+  while (*q) {
+    if (*q++ == PATHSEP)
+      p = q;
+  }
+  if (*p == '-')
+    p++;
+  pn__name = p;
+}
+
+#undef PATHSEP
+
+/*----- That's all, folks -------------------------------------------------*/
diff --git a/ui/quis.h b/ui/quis.h
new file mode 100644 (file)
index 0000000..652a9f4
--- /dev/null
+++ b/ui/quis.h
@@ -0,0 +1,89 @@
+/* -*-c-*-
+ *
+ * Setting the program name
+ *
+ * (c) 1998 Straylight/Edgeware
+ */
+
+/*----- Licensing notice --------------------------------------------------*
+ *
+ * This file is part of the mLib utilities library.
+ *
+ * mLib is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * mLib is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with mLib; if not, write to the Free
+ * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+ * MA 02111-1307, USA.
+ */
+
+#ifndef MLIB_PROGNAME_H
+#define MLIB_PROGNAME_H
+
+#ifdef __cplusplus
+  extern "C" {
+#endif
+
+/*----- Header files ------------------------------------------------------*/
+
+#include <stdio.h>
+
+/*----- Global variables --------------------------------------------------*/
+
+extern const char *pn__name;
+
+/*----- Functions provided ------------------------------------------------*/
+
+/* --- @quis@ --- *
+ *
+ * Arguments:  ---
+ *
+ * Returns:    Pointer to the program name.
+ *
+ * Use:                Returns the program name.
+ */
+
+extern const char *quis(void);
+#define QUIS (pn__name)
+
+/* --- @ego@ --- *
+ *
+ * Arguments:  @const char *p@ = pointer to program name
+ *
+ * Returns:    ---
+ *
+ * Use:                Tells mLib what the program's name is.
+ */
+
+extern void ego(const char */*p*/);
+
+/* --- @pquis@ --- *
+ *
+ * Arguments:  @FILE *fp@ = output stream to write on
+ *             @const char *p@ = pointer to string to write
+ *
+ * Returns:    Zero if everything worked, EOF if not.
+ *
+ * Use:                Writes the string @p@ to the output stream @fp@.  Occurrences
+ *             of the character `$' in @p@ are replaced by the program name
+ *             as reported by @quis@.  A `$$' is replaced by a single `$'
+ *             sign.
+ */
+
+extern int pquis(FILE */*fp*/, const char */*p*/);
+
+/*----- That's all, folks -------------------------------------------------*/
+
+#ifdef __cplusplus
+  }
+#endif
+
+#endif
diff --git a/ui/report.3 b/ui/report.3
new file mode 100644 (file)
index 0000000..1ccecb6
--- /dev/null
@@ -0,0 +1,44 @@
+.\" -*-nroff-*-
+.TH report 3 "20 June 1999" "Straylight/Edgeware" "mLib utilities library"
+.SH NAME
+report \- report errors
+.\" @moan
+.\" @die
+.SH SYNOPSIS
+.nf
+.B "#include <mLib/report.h>"
+
+.BI "void moan(const char *" f ", ...);"
+.BI "void die(int " status ", const char *" f ", ...);"
+.fi
+.SH DESCRIPTION
+The
+.B moan
+function emits a message to the standard error stream consisting of the
+program's name (as read by the
+.B quis
+function; see
+.BR quis (3) for details),
+a colon, a space, and the
+.BR printf -style
+formatted string
+.I f
+followed by a newline.  This is a handy way to report nonfatal errors in
+a program.
+.PP
+The
+.B die
+function emits a message to the standard error stream, just as for
+.B moan
+above, and then calls the
+.B exit
+function with argument
+.I status
+to halt the program.  This is a handy way to report fatal errors in a
+program.
+.SH SEE ALSO
+.BR exit (3),
+.BR quis (3),
+.BR mLib (3).
+.SH AUTHOR
+Mark Wooding, <mdw@distorted.org.uk>
diff --git a/ui/report.c b/ui/report.c
new file mode 100644 (file)
index 0000000..84a0f8d
--- /dev/null
@@ -0,0 +1,83 @@
+/* -*-c-*-
+ *
+ * Reporting errors and things
+ *
+ * (c) 1998 Straylight/Edgeware
+ */
+
+/*----- Licensing notice --------------------------------------------------*
+ *
+ * This file is part of the mLib utilities library.
+ *
+ * mLib is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * mLib is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with mLib; if not, write to the Free
+ * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+ * MA 02111-1307, USA.
+ */
+
+/*----- Header files ------------------------------------------------------*/
+
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "quis.h"
+#include "report.h"
+
+/*----- Functions provided ------------------------------------------------*/
+
+/* --- @moan@ --- *
+ *
+ * Arguments:  @const char *f@ = a @printf@-style format string
+ *             @...@ = other arguments
+ *
+ * Returns:    ---
+ *
+ * Use:                Reports an error.
+ */
+
+void moan(const char *f, ...)
+{
+  va_list ap;
+  va_start(ap, f);
+  fprintf(stderr, "%s: ", QUIS);
+  vfprintf(stderr, f, ap);
+  va_end(ap);
+  putc('\n', stderr);
+}
+
+/* --- @die@ --- *
+ *
+ * Arguments:  @int status@ = exit status to return
+ *             @const char *f@ = a @printf@-style format string
+ *             @...@ = other arguments
+ *
+ * Returns:    Never.
+ *
+ * Use:                Reports an error and exits.  Like @moan@ above, only more
+ *             permanent.
+ */
+
+void die(int status, const char *f, ...)
+{
+  va_list ap;
+  va_start(ap, f);
+  fprintf(stderr, "%s: ", QUIS);
+  vfprintf(stderr, f, ap);
+  va_end(ap);
+  putc('\n', stderr);
+  exit(status);
+}
+
+/*----- That's all, folks -------------------------------------------------*/
diff --git a/ui/report.h b/ui/report.h
new file mode 100644 (file)
index 0000000..4266cc2
--- /dev/null
@@ -0,0 +1,77 @@
+/* -*-c-*-
+ *
+ * Reporting errors and things
+ *
+ * (c) 1998 Straylight/Edgeware
+ */
+
+/*----- Licensing notice --------------------------------------------------*
+ *
+ * This file is part of the mLib utilities library.
+ *
+ * mLib is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * mLib is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with mLib; if not, write to the Free
+ * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+ * MA 02111-1307, USA.
+ */
+
+#ifndef MLIB_REPORT_H
+#define MLIB_REPORT_H
+
+#ifdef __cplusplus
+  extern "C" {
+#endif
+
+/*----- Header files ------------------------------------------------------*/
+
+#ifndef MLIB_MACROS_H
+#  include "macros.h"
+#endif
+
+/*----- Functions provided ------------------------------------------------*/
+
+/* --- @moan@ --- *
+ *
+ * Arguments:  @const char *f@ = a @printf@-style format string
+ *             @...@ = other arguments
+ *
+ * Returns:    ---
+ *
+ * Use:                Reports an error.
+ */
+
+extern void PRINTF_LIKE(1, 2)
+  moan(const char *f, ...);
+
+/* --- @die@ --- *
+ *
+ * Arguments:  @int status@ = exit status to return
+ *             @const char *f@ = a @printf@-style format string
+ *             @...@ = other arguments
+ *
+ * Returns:    Never.
+ *
+ * Use:                Reports an error and exits.  Like @moan@ above, only more
+ *             permanent.
+ */
+
+extern void PRINTF_LIKE(2, 3) NORETURN
+  die(int status, const char *f, ...);
+
+/*----- That's all, folks -------------------------------------------------*/
+
+#ifdef __cplusplus
+  }
+#endif
+
+#endif
diff --git a/utils/Makefile.am b/utils/Makefile.am
new file mode 100644 (file)
index 0000000..2f6ed7c
--- /dev/null
@@ -0,0 +1,82 @@
+### -*-makefile-*-
+###
+### Build script for utilities
+###
+### (c) 2009 Straylight/Edgeware
+###
+
+###----- Licensing notice ---------------------------------------------------
+###
+### This file is part of the mLib utilities library.
+###
+### mLib is free software; you can redistribute it and/or modify
+### it under the terms of the GNU Library General Public License as
+### published by the Free Software Foundation; either version 2 of the
+### License, or (at your option) any later version.
+###
+### mLib is distributed in the hope that it will be useful,
+### but WITHOUT ANY WARRANTY; without even the implied warranty of
+### MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+### GNU Library General Public License for more details.
+###
+### You should have received a copy of the GNU Library General Public
+### License along with mLib; if not, write to the Free
+### Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+### MA 02111-1307, USA.
+
+include $(top_srcdir)/vars.am
+
+noinst_LTLIBRARIES      = libutils.la
+libutils_la_SOURCES     =
+
+###--------------------------------------------------------------------------
+### Component files.
+
+## Compiler checking.
+pkginclude_HEADERS     += compiler.h
+LIBMANS                        += compiler.3
+
+## Utility macros.
+pkginclude_HEADERS     += macros.h
+LIBMANS                        += macros.3
+
+## Alignment.
+pkginclude_HEADERS     += align.h
+LIBMANS                        += align.3
+
+## Bit manipulation.
+pkginclude_HEADERS     += bits.h
+LIBMANS                        += bits.3
+
+check_PROGRAMS         += t/bits.t
+t_bits_t_SOURCES        = t/bits-test.c
+t_bits_t_CPPFLAGS       = $(TEST_CPPFLAGS)
+t_bits_t_LDFLAGS        = -static
+EXTRA_DIST             += t/bits-testgen.py
+
+## Exceptions.
+pkginclude_HEADERS     += exc.h
+libutils_la_SOURCES    += exc.c
+LIBMANS                        += exc.3
+
+check_PROGRAMS         += t/exc.t
+t_exc_t_SOURCES                 = t/exc-test.c
+t_exc_t_LDFLAGS                 = -static
+
+## String handling.
+pkginclude_HEADERS     += str.h
+libutils_la_SOURCES    += str.c
+LIBMANS                        += str.3
+
+## Version comparison.
+pkginclude_HEADERS     += versioncmp.h
+libutils_la_SOURCES    += versioncmp.c
+LIBMANS                        += versioncmp.3
+
+check_PROGRAMS         += t/versioncmp.t
+t_versioncmp_t_SOURCES  = t/versioncmp-test.c
+t_versioncmp_t_CPPFLAGS         = $(TEST_CPPFLAGS)
+t_versioncmp_t_LDFLAGS  = -static
+EXTRA_DIST             += t/versioncmp.tests
+
+###----- That's all, folks --------------------------------------------------
diff --git a/utils/align.3 b/utils/align.3
new file mode 100644 (file)
index 0000000..9ee2377
--- /dev/null
@@ -0,0 +1,26 @@
+.\" -*-nroff-*-
+.TH align 3 "4 January 2009" "Straylight/Edgeware" "mLib utilities library"
+.SH NAME
+align \- alignment utilities
+.\" @ALIGN
+.SH SYNOPSIS
+.nf
+.B "#include <mLib/align.h>"
+
+.BI "size_t ALIGN(size_t " sz ");"
+.fi
+.SH DESCRIPTION
+The
+.B ALIGN
+macro returns the value of its argument
+.I sz
+rounded up to the next multiple of the size of
+.BR "union align" ,
+which is defined as a union containing a selection of built-in types.
+The intent is to write fairly portable memory allocators, which must
+return correctly-aligned memory.
+.IR array .
+.SH "SEE ALSO"
+.BR mLib (3).
+.SH "AUTHOR"
+Mark Wooding, <mdw@distorted.org.uk>
diff --git a/utils/align.h b/utils/align.h
new file mode 100644 (file)
index 0000000..196c8a2
--- /dev/null
@@ -0,0 +1,59 @@
+/* -*-c-*-
+ *
+ * Pointer alignment hack
+ *
+ * (c) 2003 Straylight/Edgeware
+ */
+
+/*----- Licensing notice --------------------------------------------------*
+ *
+ * This file is part of the mLib utilities library.
+ *
+ * mLib is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * mLib is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with mLib; if not, write to the Free
+ * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+ * MA 02111-1307, USA.
+ */
+
+#ifndef MLIB_ALIGN_H
+#define MLIB_ALIGN_H
+
+#ifdef __cplusplus
+  extern "C" {
+#endif
+
+/*----- Data structures ---------------------------------------------------*/
+
+union align {
+  int i;
+  long l;
+  double d;
+  void *p;
+  void (*f)(void *);
+  struct notexist *s;
+};
+
+/*----- Macros provided ---------------------------------------------------*/
+
+#define ALIGN(sz) do {                                                 \
+  sz += sizeof(union align) - 1;                                       \
+  sz -= sz % sizeof(union align);                                      \
+} while (0)
+
+/*----- That's all, folks -------------------------------------------------*/
+
+#ifdef __cplusplus
+  }
+#endif
+
+#endif
diff --git a/utils/bits.3 b/utils/bits.3
new file mode 100644 (file)
index 0000000..a002e24
--- /dev/null
@@ -0,0 +1,713 @@
+.\" -*-nroff-*-
+.TH bits 3 "20 June 1999" "Straylight/Edgeware" "mLib utilities library"
+.ie t \{\
+.  ds ss \s8\u
+.  ds se \d\s0
+.\}
+.el \{\
+.  ds ss ^
+.  ds se
+.\}
+.SH NAME
+bits \- portable bit manipulation macros
+.\" octet
+.\" uint16
+.\" uint24
+.\" uint32
+.\" uint64
+.\" kludge64
+.\"
+.\" MASK_8
+.\" MASK_16
+.\" MASK_16_L
+.\" MASK_16_B
+.\" MASK_24
+.\" MASK_24_L
+.\" MASK_24_B
+.\" MASK_32
+.\" MASK_32_L
+.\" MASK_32_B
+.\" MASK_64
+.\" MASK_64_L
+.\" MASK_64_B
+.\"
+.\" SZ_8
+.\" SZ_16
+.\" SZ_16_L
+.\" SZ_16_B
+.\" SZ_24
+.\" SZ_24_L
+.\" SZ_24_B
+.\" SZ_32
+.\" SZ_32_L
+.\" SZ_32_B
+.\" SZ_64
+.\" SZ_64_L
+.\" SZ_64_B
+.\"
+.\" TY_8
+.\" TY_16
+.\" TY_16_L
+.\" TY_16_B
+.\" TY_24
+.\" TY_24_L
+.\" TY_24_B
+.\" TY_32
+.\" TY_32_L
+.\" TY_32_B
+.\" TY_64
+.\" TY_64_L
+.\" TY_64_B
+.\"
+.\" DOUINTSZ
+.\" DOUINTCONV
+.\"
+.\" @U8
+.\" @U16
+.\" @U24
+.\" @U32
+.\" @U64
+.\" @U64_
+.\"
+.\" @LSL8
+.\" @LSR8
+.\" @LSL16
+.\" @LSR16
+.\" @LSL24
+.\" @LSR24
+.\" @LSL32
+.\" @LSR32
+.\" @LSL64
+.\" @LSR64
+.\" @LSL64_
+.\" @LSR64_
+.\"
+.\" @ROL8
+.\" @ROR8
+.\" @ROL16
+.\" @ROR16
+.\" @ROL24
+.\" @ROR24
+.\" @ROL32
+.\" @ROL32
+.\" @ROL64
+.\" @ROR64
+.\" @ROL64_
+.\" @ROR64_
+.\"
+.\" ENDSWAP16
+.\" ENDSWAP32
+.\" ENDSWAP64
+.\"
+.\" BTOH16
+.\" LTOH16
+.\" HTOB16
+.\" HTOL16
+.\" BTOH32
+.\" LTOH32
+.\" HTOB32
+.\" HTOL32
+.\" BTOH64
+.\" LTOH64
+.\" HTOB64
+.\" HTOL64
+.\"
+.\" RAW8
+.\" RAW16
+.\" RAW32
+.\" RAW64
+.\"
+.\" @GETBYTE
+.\" @PUTBYTE
+.\"
+.\" @LOAD8
+.\" @STORE8
+.\"
+.\" @LOAD16_L
+.\" @LOAD16_B
+.\" @LOAD16
+.\" @STORE16_L
+.\" @STORE16_B
+.\" @STORE16
+.\"
+.\" @LOAD24_L
+.\" @LOAD24_B
+.\" @LOAD24
+.\" @STORE24_L
+.\" @STORE24_B
+.\" @STORE24
+.\"
+.\" @LOAD32_L
+.\" @LOAD32_B
+.\" @LOAD32
+.\" @STORE32_L
+.\" @STORE32_B
+.\" @STORE32
+.\"
+.\" @LOAD64_L
+.\" @LOAD64_B
+.\" @LOAD64
+.\" @STORE64_L
+.\" @STORE64_B
+.\" @STORE64
+.\"
+.\" @LOAD64_L_
+.\" @LOAD64_B_
+.\" @LOAD64_
+.\" @STORE64_L_
+.\" @STORE64_B_
+.\" @STORE64_
+.\"
+.\" @SET64
+.\" @X64
+.\" @ASSIGN64
+.\" @HI64
+.\" @LO64
+.\" @GET64
+.\" @AND64
+.\" @OR64
+.\" @XOR64
+.\" @CPL64
+.\" @ADD64
+.\" @SUB64
+.\" @CMP64
+.\" @ZERO64
+.SH SYNOPSIS
+.nf
+.B "#include <mLib/bits.h>"
+
+.BR "typedef " ... " octet;"
+.BR "typedef " ... " uint16;"
+.BR "typedef " ... " uint24;"
+.BR "typedef " ... " uint32;"
+.BR "typedef " ... " uint64;"
+.BR "typedef " ... " kludge64;"
+
+.BI "#define TY_" we " " type
+.BI "#define SZ_" we " \fR..."
+.BI "#define MASK_" we " \fR..."
+
+.BI "#define DOUINTSZ(" f ") \fR..."
+.BI "#define DOUINTCONV(" f ") \fR..."
+
+.IB type " U" w ( v );
+
+.IB type " LSL" w ( type " " v ", int " s );
+.IB type " LSR" w ( type " " v ", int " s );
+.IB type " ROL" w ( type " " v ", int " s );
+.IB type " ROR" w ( type " " v ", int " s );
+
+.BI "octet GETBYTE(void *" p ", size_t " o );
+.BI "void PUTBYTE(void *" p ", size_t " o ", octet " v );
+
+.IB type " LOAD" we "(void *" p );
+.BI "void STORE" we "(void *" p ", " type " " v );
+
+.BI "void SET64(kludge64 &" d ", uint32 " h ", uint32 " l );
+.BI "kludge64 X64(" hexh ", " hexl );
+.BI "void ASSIGN64(kludge64 &" d ", " x );
+.BI "uint32 HI64(kludge64" x );
+.BI "uint32 LO64(kludge64" x );
+.IB ty " GET64(" ty ", kludge64 " x );
+.BI "void AND64(kludge64 &" d ", kludge64 " x ", kludge64 " y );
+.BI "void OR64(kludge64 &" d ", kludge64 " x ", kludge64 " y );
+.BI "void XOR64(kludge64 &" d ", kludge64 " x ", kludge64 " y );
+.BI "void CPL64(kludge64 &" d ", kludge64 " x );
+.BI "void ADD64(kludge64 &" d ", kludge64 " x ", kludge64 " y );
+.BI "void SUB64(kludge64 &" d ", kludge64 " x ", kludge64 " y );
+.BI "int CMP64(kludge64 " x ", " op ", kludge64 " y );
+.BI "int ZERO64(kludge64 " x );
+.fi
+.SH DESCRIPTION
+The header file
+.B <mLib/bits.h>
+contains a number of useful definitions for portably dealing with bit-
+and byte-level manipulation of larger quantities.  The various macros
+and types are named fairly systematically.
+.PP
+The header provides utilities for working with 64-bit quantities, but a
+64-bit integer type is not guaranteed to exist under C89 rules.  This
+header takes two approaches.  Firstly, if a 64-bit type is found, the
+header defines the macro
+.B HAVE_UINT64
+and defines the various
+.RB ... 64
+macros as described below.  Secondly, it unconditionally defines a type
+.B kludge64
+and a family of macros for working with them.  See below for details.
+.
+.SS "Type definitions"
+A number of types are defined.
+.TP
+.B octet
+Equivalent to
+.BR "unsigned char" .
+This is intended to be used when a character array is used to represent
+the octets of some external data format.  Note that on some
+architectures the
+.B "unsigned char"
+type may occupy more than 8 bits.
+.TP
+.B uint16
+Equivalent to
+.BR "unsigned short" .
+Intended to be used when a 16-bit value is required.  This type is
+always capable of representing any 16-bit unsigned value, but the actual
+type may be wider than 16 bits and will require masking.
+.TP
+.B uint24
+Equivalent to some (architecture-dependent) standard type.  Capable of
+representing any unsigned 24-bit value, although the the actual type may
+be wider than 24 bits.
+.TP
+.B uint32
+Equivalent to some (architecture-dependent) standard type.  Capable of
+representing any unsigned 32-bit value, although the the actual type may
+be wider than 32 bits.
+pp.TP
+.B uint64
+Equivalent to some (architecture-dependent) standard type, if it exists.
+Capable of representing any unsigned 64-bit value, although the the
+actual type may be wider than 64 bits.
+.
+.SS "Size/endianness suffixes"
+Let
+.I w
+be one of the size suffixes: 8, 16, 24, 32, and (if available) 64.
+Furthermore, let
+.I we
+be one of the size-and-endian suffixes
+.IR w ,
+or, where
+.IR w \~>\~8,
+.IB w _L
+or
+.IB w _B \fR,
+where
+.RB ` _L '
+denotes little-endian (Intel, VAX) representation, and
+.RB ` _B '
+denotes big-endian (IBM, network) representation; omitting an explicit
+suffix gives big-endian order by default, since this is most common in
+portable data formats.
+.PP
+The macro invocation
+.BI DOUINTSZ( f )
+invokes a given macro
+.I f
+repeatedly, as
+.IB f ( w )
+for each size suffix
+.I w
+listed above.
+.PP
+The macro invocation
+.BI DOUINTCONV( f )
+invokes a given macro
+.I f
+repeatedly, as
+.IR f ( w ", " we ", " suff )
+where
+.I we
+ranges over size-and-endian suffixes as described above,
+.I w
+is just the corresponding bit width, as an integer, and
+.I suff
+is a suffix
+.IR w ,
+.IB w l\fR,
+or
+.IB w b\fR,
+suitable for a C function name.
+.PP
+These macros are intended to be used to define families of related
+functions.
+.
+.SS "Utility macros"
+For each size-and-endian suffix
+.IR we ,
+the following macros are defined.
+.TP
+.BI TY_ we
+A synonym for the appropriate one of the types
+.BR octet ,
+.BR uint32 ,
+etc.\& listed above.
+.TP
+.BI SZ_ we
+The number of octets needed to represent a value of the corresponding
+type; i.e., this is
+.IR w /8.
+.TP
+.BI MASK_ we
+The largest integer representable in the corresponding type; i.e., this
+is
+.RI 2\*(ss w \*(se\~\-\~1.
+.PP
+(Note that the endianness suffix is irrelevant in the above
+definitions.)
+.PP
+For each size suffix
+.IR w ,
+the macro invocation
+.BI U w ( x )
+coerces an integer
+.I x
+to the appropriate type; specifically, it returns the smallest
+nonnegative integer congruent to
+.I x
+(modulo
+.RI 2\*(ss w \*(se).
+.
+.SS "Shift and rotate"
+For each size suffix
+.IR w ,
+the macro invocations
+.BI LSL w ( x ", " n )
+and
+.BI LSR w ( x ", " n )
+shift a
+.IR w -bit
+quantity
+.I x
+left or right, respectively, by
+.I n
+places; if
+.IR n \~\(>=\~ w
+then
+.I n
+is reduced modulo
+.IR w .
+(This behaviour is unfortunate, but (a) it's what a number of CPUs
+provide natively, and (b) it's a cheap way to prevent undefined
+behaviour.)  Similarly,
+.BI ROL w ( x ", " n )
+and
+.BI ROR w ( x ", " n )
+rotate a
+.IR w -bit
+quantity
+.I x
+left or right, respectively, by
+.I n
+places.
+.
+.SS "Byte order conversions"
+For each size suffix
+.IR w ,
+the macro invocation
+.BI ENDSWAP w ( x )
+returns the
+.IR w -bit
+value
+.IR x
+with its bytes reversed.  The
+.B ENDSWAP8
+macro does nothing (except truncate its operand to 8 bits), but is
+provided for the sake of completeness.
+.PP
+A
+.I big-endian
+representation stores the most significant octet of an integer at the
+lowest address, with the following octets in decreasing order of
+significance.  A
+.I little-endian
+representation instead stores the
+.I least
+significant octet at the lowest address, with the following octets in
+increasing order of significance.  An environment has a preferred order
+for arranging the constituent octets of an integer of some given size in
+memory; this might be either the big- or little-endian representation
+just described, or something else strange.
+.PP
+It might be possible to rearrange the bits in an integer so that, when
+that integer is stored to memory in the environment's preferred manner,
+you end up with the big- or little-endian representation of the original
+integer; and, similarly, it might be possible to load a big- or
+little-endian representation of an integer into a variable using the
+environment's preferred ordering and then rearrange the bits so as to
+recover the integer value originally represented.  If the environment is
+sufficiently strange, these things might not be possible, but this is
+actually quite rare.
+.PP
+Say that an integer has been converted to
+.I big-
+or
+.I "little-endian form"
+if, when it is stored in memory in the environment's preferred manner,
+one ends up with a big- or little-endian representation of the original
+integer.  Equivalently, if one starts with a big- or little-endian
+representation of some integer, and loads it into a variable using the
+environment's preferred manner, one ends up with the big- or
+little-endian form of the original integer.
+.PP
+If these things are possible, then the following macros are defined.
+.TP
+.BI HTOL w ( x )
+Convert a
+.IR w -bit
+integer
+.I x
+to little-endian form.
+.TP
+.BI HTOB w ( x )
+Convert a
+.IR w -bit
+integer
+.I x
+to big-endian form.
+.TP
+.BI LTOH w ( x )
+Convert a
+.IR w -bit
+integer
+.I x
+from little-endian form.
+.TP
+.BI BTOH w ( x )
+Convert a
+.IR w -bit
+integer
+.I x
+from big-endian form.
+.
+.SS "Load and store"
+The macro invocation
+.BI GETBYTE( p ", " o )
+returns the
+.IR o th
+octet following the address
+.IR p .
+Conversely,
+.BI PUTBYTE( p ", " o ", " v)
+stores
+.I
+v in the
+.IR o th
+byte following the address
+.IR p .
+These macros always operate on byte offsets regardless of the type of
+the pointer
+.IR p .
+.PP
+For each size suffix
+.IR w ,
+there may be a macro such that the invocation
+.BI RAW w ( p )
+is an lvalue designating the
+.IR w /8
+octets starting at address
+.IR p ,
+interpreted according to the environment's preferred representation,
+except that
+.I p
+need not be aligned in any particular fashion.  There are many reasons
+why this might not be possible; programmers are not normally expected to
+use these macros directly, and they are documented in case they are
+useful for special effects.
+.PP
+For each size-and-endian suffix
+.IR we ,
+the macro invocation
+.BI LOAD we ( p )
+loads and returns a value in the corresponding format at address
+.IR p ;
+similarly,
+.BI STORE we ( p ", " x )
+stores the value
+.I x
+at address
+.I p
+in the corresponding format.
+.
+.SS "64-bit support"
+For portability to environments without native 64-bit integers, the
+structure
+.B kludge64
+is defined.  If the target platform is known to have an unsigned 64-bit
+integer type, then this structure merely encapsulates a native integer,
+and a decent optimizing compiler can be expected to handle this exactly
+as if it were the native type.  Otherwise, it contains two 32-bit halves
+which are processed the hard way.
+.PP
+For each of the above macros with a suffix
+.BR 64 ,
+.BR 64_L ,
+or
+.BR 64_B ,
+an additional `kludge' macro is defined, whose name has an additional
+final underscore; e.g., the kludge macro corresponding to
+.B ROR64
+is
+.BR ROR64_ ;
+and that corresponding to
+.B LOAD64_L
+is
+.BR LOAD64_L_ .
+If the original macro would have
+.I returned
+a value of type
+.BR uint64 ,
+then the kludge macro has an additional first argument, denoted
+.IR d ,
+which should be an lvalue of type
+.BR kludge64 ,
+and the kludge macro will store its result in
+.IR d .
+The kludge macro's remaining arguments are the same as the original
+macro, except that where the original macro accepts an argument of type
+.BR uint64 ,
+the kludge macro accepts an argument of type
+.B kludge64
+instead.
+.PP
+Finally, a number of additional macros are provided, to make working
+with
+.B kludge64
+somewhat less awful.
+.TP
+.BI SET64( d ", " h ", " l )
+Set the high 32 bits of
+.I d
+to be
+.IR h ,
+and the low 32 bits to be
+.IR l .
+Both
+.I h
+and
+.I l
+may be arbitrary integers.
+.TP
+.BI X64( hexh ", " hexl )
+Expands to an initializer for an object of type
+.B kludge64
+where
+.I hexh
+and
+.I hexl
+encode the high and low 32-bit halves in hexadecimal, without any
+.B 0x
+prefix.
+.TP
+.BI ASSIGN( d ", " x )
+Make
+.I d
+be a copy of the
+.B kludge64
+.IR x .
+.TP
+.BI HI64( x )
+Return the high 32 bits of
+.IR x .
+.TP
+.BI LO64( x )
+Return the low 32 bits of
+.IR x .
+.TP
+.BI GET64( t ", " x )
+Return the value of
+.I x
+as a value of type
+.IR t .
+If
+.I t
+is an unsigned integer type, then the value will be truncated to fit as
+necessary; if
+.I t
+is a signed integer type, then the behaviour is undefined if the value
+of
+.I x
+is too large.
+.TP
+.BI AND64( d ", " x ", " y )
+Set
+.I d
+to be the bitwise-and of the two
+.B kludge64
+arguments
+.I x
+and
+.IR y .
+.TP
+.BI OR64( d ", " x ", " y )
+Set
+.I d
+to be the bitwise-or of the two
+.B kludge64
+arguments
+.I x
+and
+.IR y .
+.TP
+.BI XOR64( d ", " x ", " y )
+Set
+.I d
+to be the bitwise-exclusive-or of the two
+.B kludge64
+arguments
+.I x
+and
+.IR y .
+.TP
+.BI CPL64( d ", " x )
+Set
+.I d
+to be the bitwise complement of the
+.B kludge64
+argument
+.IR x .
+.TP
+.BI ADD64( d ", " x ", " y )
+Set
+.I d
+to be the sum of the two
+.B kludge64
+arguments
+.I x
+and
+.IR y .
+.TP
+.BI SUB64( d ", " x ", " y )
+Set
+.I d
+to be the difference of the two
+.B kludge64
+arguments
+.I x
+and
+.IR y .
+.TP
+.BI CMP64( x ", " op ", " y )
+Here,
+.I x
+and
+.I y
+should be arguments of type
+.B kludge64
+and
+.I op
+should be one of the relational operators
+.BR == ,
+.BR < ,
+.BR <= ,
+.BR > ,
+or
+.B >=
+\(en
+.I not
+.BR !=.
+Evaluates nonzero if
+.IR x \~ op \~ y .
+.TP
+.BI ZERO64( x )
+Evaluates nonzero if the
+.B kludge64
+argument
+.I x
+is exactly zero.
+.SH "SEE ALSO"
+.BR mLib (3).
+.SH AUTHOR
+Mark Wooding, <mdw@distorted.org.uk>
+
diff --git a/utils/bits.h b/utils/bits.h
new file mode 100644 (file)
index 0000000..656eee5
--- /dev/null
@@ -0,0 +1,683 @@
+/* -*-c-*-
+ *
+ * Portable bit-level manipulation macros
+ *
+ * (c) 1998 Straylight/Edgeware
+ */
+
+/*----- Licensing notice --------------------------------------------------*
+ *
+ * This file is part of the mLib utilities library.
+ *
+ * mLib is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * mLib is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with mLib; if not, write to the Free
+ * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+ * MA 02111-1307, USA.
+ */
+
+#ifndef MLIB_BITS_H
+#define MLIB_BITS_H
+
+#ifdef __cplusplus
+  extern "C" {
+#endif
+
+/*----- Header files ------------------------------------------------------*/
+
+#include <limits.h>
+#include <stddef.h>
+#if __STDC_VERSION__ >= 199900l
+#  include <stdint.h>
+#endif
+
+#ifndef MLIB_COMPILER_H
+#  include "compiler.h"
+#endif
+
+/*----- Decide on some types ----------------------------------------------*/
+
+/* --- Make GNU C shut up --- */
+
+#if __GNUC__ > 2 || (__GNUC__ == 2 && __GNUC_MINOR__ >= 91)
+#  define MLIB_BITS_EXTENSION __extension__
+#else
+#  define MLIB_BITS_EXTENSION
+#endif
+
+/* --- Decide on a 32-bit type --- *
+ *
+ * I want a type which is capable of expressing 32-bit numbers.  Because some
+ * implementations have 64-bit @long@s (infinitely preferable to the abortion
+ * that is @long long@), using @unsigned long@ regardless is wasteful.  So,
+ * if @int@ appears to be good enough, then I'll go with that.
+ */
+
+#if UINT_MAX >= 0xffffffffu
+  typedef unsigned int uint32;
+#else
+  typedef unsigned long uint32;
+#endif
+
+/* --- Decide on a 64-bit type --- *
+ *
+ * The test is quite subtle.  Think about it.  Note that (at least on my
+ * machine), the 32-bit macros are *much* faster than GCC's @long long@
+ * support.
+ */
+
+#if defined(ULONG_LONG_MAX) && !defined(ULLONG_MAX)
+#  define ULLONG_MAX ULONG_LONG_MAX
+#endif
+
+#if UINT_MAX >> 31 > 0xffffffff
+#  define HAVE_UINT64
+   typedef unsigned int uint64;
+#elif ULONG_MAX >> 31 > 0xffffffff
+#  define HAVE_UINT64
+   typedef unsigned long uint64;
+#elif defined(ULLONG_MAX)
+#  define HAVE_UINT64
+   MLIB_BITS_EXTENSION typedef unsigned long long uint64;
+#endif
+
+#ifdef DEBUG64
+#  undef HAVE_UINT64
+#endif
+
+#ifdef HAVE_UINT64
+  typedef struct { uint64 i; } kludge64;
+#else
+  typedef struct { uint32 hi, lo; } kludge64;
+#endif
+
+/* --- Decide on a 24-bit type --- */
+
+#if UINT_MAX >= 0x00ffffffu
+  typedef unsigned int uint24;
+#else
+  typedef unsigned long uint24;
+#endif
+
+/* --- Decide on 16-bit and 8-bit types --- *
+ *
+ * This is more for brevity than anything else.
+ */
+
+typedef unsigned short uint16;
+typedef unsigned char octet, uint8;
+
+/* --- WARNING! --- *
+ *
+ * Never lose sight of the fact that the above types may be wider than the
+ * names suggest.  Some architectures have 32-bit @short@s for example.
+ */
+
+/*----- Macros ------------------------------------------------------------*/
+
+/* --- Useful masks --- */
+
+#define MASK8 0xffu
+#define MASK16 0xffffu
+#define MASK16_L MASK16
+#define MASK16_B MASK16
+#define MASK24 0xffffffu
+#define MASK24_L MASK24
+#define MASK24_B MASK24
+#define MASK32 0xffffffffu
+#define MASK32_L MASK32
+#define MASK32_B MASK32
+
+#ifdef HAVE_UINT64
+#  define MASK64 MLIB_BITS_EXTENSION 0xffffffffffffffffu
+#  define MASK64_L MASK64
+#  define MASK64_B MASK64
+#endif
+
+/* --- Sizes --- */
+
+#define SZ_8 1
+#define SZ_16 2
+#define SZ_16_L 2
+#define SZ_16_B 2
+#define SZ_24 3
+#define SZ_24_L 3
+#define SZ_24_B 3
+#define SZ_32 4
+#define SZ_32_L 4
+#define SZ_32_B 4
+
+#ifdef HAVE_UINT64
+#  define SZ_64 8
+#  define SZ_64_L 8
+#  define SZ_64_B 8
+#endif
+
+/* --- Type aliases --- */
+
+#define TY_U8 octet
+#define TY_U16 uint16
+#define TY_U16_L uint16
+#define TY_U16_B uint16
+#define TY_U24 uint24
+#define TY_U24_L uint24
+#define TY_U24_B uint24
+#define TY_U32 uint32
+#define TY_U32_L uint32
+#define TY_U32_B uint32
+
+#ifdef HAVE_UINT64
+#  define TY_U64 uint64
+#  define TY_U64_L uint64
+#  define TY_U64_B uint64
+#endif
+
+/* --- List macros --- */
+
+#ifdef HAVE_UINT64
+#  define  DOUINTCONV(_)                                               \
+     _(8, 8, 8)                                                                \
+     _(16, 16, 16) _(16, 16_L, 16l) _(16, 16_B, 16b)                   \
+     _(24, 24, 24) _(24, 24_L, 24l) _(24, 24_B, 24b)                   \
+     _(32, 32, 32) _(32, 32_L, 32l) _(32, 32_B, 32b)                   \
+     _(64, 64, 64) _(64, 64_L, 64l) _(64, 64_B, 64b)
+#  define DOUINTSZ(_) _(8) _(16) _(24) _(32) _(64)
+#else
+#  define  DOUINTCONV(_)                                               \
+     _(8, 8, 8)                                                                \
+     _(16, 16, 16) _(16, 16_L, 16l) _(16, 16_B, 16b)                   \
+     _(24, 24, 24) _(24, 24_L, 24l) _(24, 24_B, 24b)                   \
+     _(32, 32, 32) _(32, 32_L, 32l) _(32, 32_B, 32b)
+#  define DOUINTSZ(_) _(8) _(16) _(24) _(32)
+#endif
+
+/* --- Type coercions --- */
+
+#define U8(x) ((octet)((x) & MASK8))
+#define U16(x) ((uint16)((x) & MASK16))
+#define U24(x) ((uint24)((x) & MASK24))
+#define U32(x) ((uint32)((x) & MASK32))
+
+#ifdef HAVE_UINT64
+#  define U64(x) ((uint64)(x) & MASK64)
+#  define U64_(d, x) ((d).i = U64(x).i)
+#else
+#  define U64_(d, x) ((d).hi = U32((x).hi), (d).lo = U32((x).lo))
+#endif
+
+/* --- Safe shifting macros --- */
+
+#define LSL8(v, s) U8(U8(v) << ((s) & 7u))
+#define LSR8(v, s) U8(U8(v) >> ((s) & 7u))
+#define LSL16(v, s) U16(U16(v) << ((s) & 15u))
+#define LSR16(v, s) U16(U16(v) >> ((s) & 15u))
+#define LSL24(v, s) U24(U24(v) << ((s) % 24u))
+#define LSR24(v, s) U24(U24(v) >> ((s) % 24u))
+#define LSL32(v, s) U32(U32(v) << ((s) & 31u))
+#define LSR32(v, s) U32(U32(v) >> ((s) & 31u))
+
+#ifdef HAVE_UINT64
+#  define LSL64(v, s) U64(U64(v) << ((s) & 63u))
+#  define LSR64(v, s) U64(U64(v) >> ((s) & 63u))
+#  define LSL64_(d, v, s) ((d).i = LSL64((v).i, (s)))
+#  define LSR64_(d, v, s) ((d).i = LSR64((v).i, (s)))
+#else
+#  define LSL64_(d, v, s) do {                                         \
+     unsigned _s = (s) & 63u;                                          \
+     uint32 _l = (v).lo, _h = (v).hi;                                  \
+     kludge64 *_d = &(d);                                              \
+     if (_s >= 32) {                                                   \
+       _d->hi = LSL32(_l, _s - 32u);                                   \
+       _d->lo = 0;                                                     \
+     } else if (!_s) {                                                 \
+       _d->lo = _l;                                                    \
+       _d->hi = _h;                                                    \
+     } else {                                                          \
+       _d->hi = LSL32(_h, _s) | LSR32(_l, 32u - _s);                   \
+       _d->lo = LSL32(_l, _s);                                         \
+     }                                                                 \
+   } while (0)
+#  define LSR64_(d, v, s) do {                                         \
+     unsigned _s = (s) & 63u;                                          \
+     uint32 _l = (v).lo, _h = (v).hi;                                  \
+     kludge64 *_d = &(d);                                              \
+     if (_s >= 32) {                                                   \
+       _d->lo = LSR32(_h, _s - 32u);                                   \
+       _d->hi = 0;                                                     \
+     } else if (!_s) {                                                 \
+       _d->lo = _l;                                                    \
+       _d->hi = _h;                                                    \
+     } else {                                                          \
+       _d->lo = LSR32(_l, _s) | LSL32(_h, 32u - _s);                   \
+       _d->hi = LSR32(_h, _s);                                         \
+     }                                                                 \
+   } while (0)
+#endif
+
+/* --- Rotation macros --- */
+
+#define ROL8(v, s) (LSL8((v), (s)) | (LSR8((v), 8u - (s))))
+#define ROR8(v, s) (LSR8((v), (s)) | (LSL8((v), 8u - (s))))
+#define ROL16(v, s) (LSL16((v), (s)) | (LSR16((v), 16u - (s))))
+#define ROR16(v, s) (LSR16((v), (s)) | (LSL16((v), 16u - (s))))
+#define ROL24(v, s) (LSL24((v), (s)) | (LSR24((v), 24u - (s))))
+#define ROR24(v, s) (LSR24((v), (s)) | (LSL24((v), 24u - (s))))
+#define ROL32(v, s) (LSL32((v), (s)) | (LSR32((v), 32u - (s))))
+#define ROR32(v, s) (LSR32((v), (s)) | (LSL32((v), 32u - (s))))
+
+#ifdef HAVE_UINT64
+#  define ROL64(v, s) (LSL64((v), (s)) | (LSR64((v), 64u - (s))))
+#  define ROR64(v, s) (LSR64((v), (s)) | (LSL64((v), 64u - (s))))
+#  define ROL64_(d, v, s) ((d).i = ROL64((v).i, (s)))
+#  define ROR64_(d, v, s) ((d).i = ROR64((v).i, (s)))
+#else
+#  define ROL64_(d, v, s) do {                                         \
+     unsigned _s = (s) & 63u;                                          \
+     uint32 _l = (v).lo, _h = (v).hi;                                  \
+     kludge64 *_d = &(d);                                              \
+     if (_s > 32) {                                                    \
+       _d->hi = LSL32(_l, _s - 32u) | LSR32(_h, 64u - _s);             \
+       _d->lo = LSL32(_h, _s - 32u) | LSR32(_l, 64u - _s);             \
+     } else if (!_s) {                                                 \
+       _d->lo = _l;                                                    \
+       _d->hi = _h;                                                    \
+     } else if (_s == 32) {                                            \
+       _d->lo = _h;                                                    \
+       _d->hi = _l;                                                    \
+     } else {                                                          \
+       _d->hi = LSL32(_h, _s) | LSR32(_l, 32u - _s);                   \
+       _d->lo = LSL32(_l, _s) | LSR32(_h, 32u - _s);                   \
+     }                                                                 \
+   } while (0)
+#  define ROR64_(d, v, s) do {                                         \
+     unsigned _s = (s) & 63u;                                          \
+     uint32 _l = (v).lo, _h = (v).hi;                                  \
+     kludge64 *_d = &(d);                                              \
+     if (_s > 32) {                                                    \
+       _d->hi = LSR32(_l, _s - 32u) | LSL32(_h, 64u - _s);             \
+       _d->lo = LSR32(_h, _s - 32u) | LSL32(_l, 64u - _s);             \
+     } else if (!_s) {                                                 \
+       _d->lo = _l;                                                    \
+       _d->hi = _h;                                                    \
+     } else if (_s == 32) {                                            \
+       _d->lo = _h;                                                    \
+       _d->hi = _l;                                                    \
+     } else {                                                          \
+       _d->hi = LSR32(_h, _s) | LSL32(_l, 32u - _s);                   \
+       _d->lo = LSR32(_l, _s) | LSL32(_h, 32u - _s);                   \
+     }                                                                 \
+   } while (0)
+#endif
+
+/* --- Endianness swapping --- */
+
+#if GCC_VERSION_P(4, 8)
+#  define ENDSWAP16(x) ((uint16)__builtin_bswap16(x))
+#endif
+#if GCC_VERSION_P(4, 3)
+#  define ENDSWAP32(x) ((uint32)__builtin_bswap32(x))
+#endif
+#if GCC_VERSION_P(4, 3) && defined(HAVE_UINT64)
+#  define ENDSWAP64(x) ((uint64)__builtin_bswap64(x))
+#endif
+
+#ifndef ENDSWAP8
+#  define ENDSWAP8(x) U8(x)
+#endif
+#ifndef ENDSWAP16
+#  define ENDSWAP16(x)                                                 \
+       ((((uint16)(x) >> 8)&0xff) |                                    \
+        (((uint16)(x)&0xff) << 8))
+#endif
+#ifndef ENDSWAP24
+#  define ENDSWAP24(x)                                                 \
+       ((((uint24)(x) >> 16)&0xff) |                                   \
+        ((uint24)(x)&0xff00) |                                         \
+        ((uint24)((x)&0xff) << 16))
+#endif
+#ifndef ENDSWAP32
+#  define ENDSWAP32(x)                                                 \
+       (ENDSWAP16(((uint32)(x) >> 16)&0xffff) |                        \
+        ((uint32)ENDSWAP16((x)&0xffff) << 16))
+#endif
+#if defined(HAVE_UINT64) && !defined(ENDSWAP64)
+#  define ENDSWAP64(x)                                                 \
+       (ENDSWAP32(((uint64)(x) >> 32)&0xffffffff) |                    \
+        ((uint64)ENDSWAP32((x)&0xffffffff) << 32))
+#endif
+#ifdef HAVE_UINT64
+#  define ENDSWAP64_(z, x)                                             \
+       ((z).i = ENDSWAP64((x).i))
+#else
+#  define ENDSWAP64_(z, x)                                             \
+       ((z).lo = ENDSWAP32((x).hi),                                    \
+        (z).hi = ENDSWAP32((x).lo))
+#endif
+
+#define MLIB_LITTLE_ENDIAN 1234
+#define MLIB_BIG_ENDIAN 4321
+#if defined(__ORDER_LITTLE_ENDIAN__) &&                                        \
+    __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
+#  define MLIB_BYTE_ORDER MLIB_LITTLE_ENDIAN
+#elif defined(__ORDER_BIG_ENDIAN__) &&                                 \
+      __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
+#  define MLIB_BYTE_ORDER MLIB_BIG_ENDIAN
+#endif
+
+#if MLIB_BYTE_ORDER == MLIB_LITTLE_ENDIAN
+#  define HTOL16(x) (x)
+#  define LTOH16(x) (x)
+#  define HTOB16(x) ENDSWAP16(x)
+#  define BTOH16(x) ENDSWAP16(x)
+#  define HTOL24(x) (x)
+#  define LTOH24(x) (x)
+#  define HTOB24(x) ENDSWAP24(x)
+#  define BTOH24(x) ENDSWAP24(x)
+#  define HTOL32(x) (x)
+#  define LTOH32(x) (x)
+#  define HTOB32(x) ENDSWAP32(x)
+#  define BTOH32(x) ENDSWAP32(x)
+#  ifdef HAVE_UINT64
+#    define HTOL64(x) (x)
+#    define LTOH64(x) (x)
+#    define HTOB64(x) ENDSWAP64(x)
+#    define BTOH64(x) ENDSWAP64(x)
+#  endif
+#  define HTOL64_(z, x) ASSIGN64(z, x)
+#  define LTOH64_(z, x) ASSIGN64(z, x)
+#  define HTOB64_(z, x) ENDSWAP64_(z, x)
+#  define BTOH64_(z, x) ENDSWAP64_(z, x)
+#elif MLIB_BYTE_ORDER == MLIB_BIG_ENDIAN
+#  define HTOL16(x) ENDSWAP16(x)
+#  define LTOH16(x) ENDSWAP16(x)
+#  define HTOB16(x) (x)
+#  define BTOH16(x) (x)
+#  define HTOL24(x) ENDSWAP24(x)
+#  define LTOH24(x) ENDSWAP24(x)
+#  define HTOB24(x) (x)
+#  define BTOH24(x) (x)
+#  define HTOL32(x) ENDSWAP32(x)
+#  define LTOH32(x) ENDSWAP32(x)
+#  define HTOB32(x) (x)
+#  define BTOH32(x) (x)
+#  ifdef HAVE_UINT64
+#    define HTOL64(x) ENDSWAP64(x)
+#    define LTOH64(x) ENDSWAP64(x)
+#    define HTOB64(x) (x)
+#    define BTOH64(x) (x)
+#    define HTOL64_(z, x) ENDSWAP64_(z, x)
+#    define LTOH64_(z, x) ENDSWAP64_(z, x)
+#    define HTOB64_(z, x) ((z).i = (x).i)
+#    define BTOH64_(z, x) ((z).i = (x).i)
+#  endif
+#  define HTOL64_(z, x) ENDSWAP64_(z, x)
+#  define LTOH64_(z, x) ENDSWAP64_(z, x)
+#  define HTOB64_(z, x) ASSIGN64(z, x)
+#  define BTOH64_(z, x) ASSIGN64(z, x)
+#endif
+
+/* --- Unaligned access (GCC-specific) --- */
+
+#if GCC_VERSION_P(3, 3) && CHAR_BIT == 8
+#  define MLIB_MISALIGNED __attribute__((aligned(1), may_alias))
+#  if __SIZEOF_SHORT__ == 2
+     typedef MLIB_MISALIGNED unsigned short misaligned_uint16;
+#    define RAW16(p) (*(misaligned_uint16 *)(p))
+#  endif
+#  if __SIZEOF_INT__ == 4
+     typedef MLIB_MISALIGNED unsigned int misaligned_uint32;
+#    define RAW32(p) (*(misaligned_uint32 *)(p))
+#  elif __SIZEOF_LONG__ == 4
+     typedef MLIB_MISALIGNED unsigned long misaligned_uint32;
+#    define RAW32(p) (*(misaligned_uint32 *)(p))
+#  endif
+#  if __SIZEOF_LONG__ == 8
+     typedef MLIB_MISALIGNED unsigned long misaligned_uint64;
+#    define RAW64(p) (*(misaligned_uint64 *)(p))
+#  elif __SIZEOF_LONG_LONG__ == 8
+     typedef MLIB_MISALIGNED unsigned long long misaligned_uint64;
+#    define RAW64(p) (*(misaligned_uint64 *)(p))
+#  endif
+#endif
+
+/* --- Storage and retrieval --- */
+
+#if defined(RAW16) && defined(LTOH16)
+#  define LOAD16_L(p) LTOH16(RAW16(p))
+#endif
+#if defined(RAW16) && defined(HTOL16)
+#  define STORE16_L(p, x) (RAW16(p) = HTOL16(x))
+#endif
+#if defined(RAW16) && defined(BTOH16)
+#  define LOAD16_B(p) BTOH16(RAW16(p))
+#endif
+#if defined(RAW16) && defined(HTOB16)
+#  define STORE16_B(p, x) (RAW16(p) = HTOB16(x))
+#endif
+
+#if defined(RAW32) && defined(LTOH32)
+#  define LOAD32_L(p) LTOH32(RAW32(p))
+#endif
+#if defined(RAW32) && defined(HTOL32)
+#  define STORE32_L(p, x) (RAW32(p) = HTOL32(x))
+#endif
+#if defined(RAW32) && defined(BTOH32)
+#  define LOAD32_B(p) BTOH32(RAW32(p))
+#endif
+#if defined(RAW32) && defined(HTOB32)
+#  define STORE32_B(p, x) (RAW32(p) = HTOB32(x))
+#endif
+
+#if defined(RAW64) && defined(LTOH64)
+#  define LOAD64_L(p) LTOH64(RAW64(p))
+#endif
+#if defined(RAW64) && defined(HTOL64)
+#  define STORE64_L(p, x) (RAW64(p) = HTOL64(x))
+#endif
+#if defined(RAW64) && defined(BTOH64)
+#  define LOAD64_B(p) BTOH64(RAW64(p))
+#endif
+#if defined(RAW64) && defined(HTOB64)
+#  define STORE64_B(p, x) (RAW64(p) = HTOB64(x))
+#endif
+
+#define GETBYTE(p, o) (((octet *)(p))[o] & MASK8)
+#define PUTBYTE(p, o, v) (((octet *)(p))[o] = U8((v)))
+
+#define LOAD8(p) (GETBYTE((p), 0))
+#define STORE8(p, v) (PUTBYTE((p), 0, (v)))
+
+#ifndef LOAD16_B
+#  define LOAD16_B(p)
+       (((uint16)GETBYTE((p), 0) << 8) |                               \
+        ((uint16)GETBYTE((p), 1) << 0))
+#endif
+#ifndef LOAD16_L
+#  define LOAD16_L(p)                                                  \
+       (((uint16)GETBYTE((p), 0) << 0) |                               \
+        ((uint16)GETBYTE((p), 1) << 8))
+#endif
+#define LOAD16(p) LOAD16_B((p))
+
+#ifndef STORE16_B
+#  define STORE16_B(p, v)                                              \
+       (PUTBYTE((p), 0, (uint16)(v) >> 8),                             \
+        PUTBYTE((p), 1, (uint16)(v) >> 0))
+#endif
+#ifndef STORE16_L
+#  define STORE16_L(p, v)                                              \
+       (PUTBYTE((p), 0, (uint16)(v) >> 0),                             \
+        PUTBYTE((p), 1, (uint16)(v) >> 8))
+#endif
+#define STORE16(p, v) STORE16_B((p), (v))
+
+#ifndef LOAD24_B
+#  define LOAD24_B(p)                                                  \
+       (((uint24)GETBYTE((p), 0)            << 16) |                   \
+        ((uint24)LOAD16_B((octet *)(p) + 1) <<  0))
+#endif
+#ifndef LOAD24_L
+#  define LOAD24_L(p)                                                  \
+       (((uint24)LOAD16_L((octet *)(p) + 0) <<  0) |                   \
+        ((uint24)GETBYTE((p), 2)            << 16))
+#endif
+#define LOAD24(p) LOAD24_B((p))
+
+#ifndef STORE24_B
+#  define STORE24_B(p, v)                                              \
+       (PUTBYTE((p), 0,             (uint24)(v) >> 16),                \
+        STORE16_B((octet *)(p) + 1, (uint24)(v) >>  0))
+#endif
+#ifndef STORE24_L
+#  define STORE24_L(p, v)                                              \
+       (STORE16_L((octet *)(p) + 0, (uint24)(v) >>  0),                \
+        PUTBYTE((p), 2,             (uint24)(v) >> 16))
+#endif
+#define STORE24(p, v) STORE24_B((p), (v))
+
+#ifndef LOAD32_B
+#  define LOAD32_B(p)                                                  \
+       (((uint32)LOAD16_B((octet *)(p) + 0) << 16) |                   \
+        ((uint32)LOAD16_B((octet *)(p) + 2) <<  0))
+#endif
+#ifndef LOAD32_L
+#  define LOAD32_L(p)                                                  \
+       (((uint32)LOAD16_L((octet *)(p) + 0) <<  0) |                   \
+        ((uint32)LOAD16_L((octet *)(p) + 2) << 16))
+#endif
+#define LOAD32(p) LOAD32_B((p))
+
+#ifndef STORE32_B
+#  define STORE32_B(p, v)                                              \
+       (STORE16_B((octet *)(p) + 0, (uint32)(v) >> 16),                \
+        STORE16_B((octet *)(p) + 2, (uint32)(v) >>  0))
+#endif
+#ifndef STORE32_L
+#  define STORE32_L(p, v)                                              \
+       (STORE16_L((octet *)(p) + 0, (uint32)(v) >>  0),                \
+        STORE16_L((octet *)(p) + 2, (uint32)(v) >> 16))
+#endif
+#define STORE32(p, v) STORE32_B((p), (v))
+
+#ifdef HAVE_UINT64
+
+#  ifndef LOAD64_B
+#    define LOAD64_B(p)                                                        \
+       (((uint64)LOAD32_B((octet *)(p) + 0) << 32) |                   \
+        ((uint64)LOAD32_B((octet *)(p) + 4) <<  0))
+#  endif
+#  ifndef LOAD64_L
+#    define LOAD64_L(p)                                                        \
+       (((uint64)LOAD32_L((octet *)(p) + 0) <<  0) |                   \
+        ((uint64)LOAD32_L((octet *)(p) + 4) << 32))
+#  endif
+#  define LOAD64(p) LOAD64_B((p))
+#  define LOAD64_B_(d, p) ((d).i = LOAD64_B((p)))
+#  define LOAD64_L_(d, p) ((d).i = LOAD64_L((p)))
+#  define LOAD64_(d, p) LOAD64_B_((d), (p))
+
+#  ifndef STORE64_B
+#    define STORE64_B(p, v)                                            \
+       (STORE32_B((octet *)(p) + 0, (uint64)(v) >> 32),                \
+        STORE32_B((octet *)(p) + 4, (uint64)(v) >>  0))
+#  endif
+#  ifndef STORE64_L
+#    define STORE64_L(p, v)                                            \
+       (STORE32_L((octet *)(p) + 0, (uint64)(v) >>  0),                \
+        STORE32_L((octet *)(p) + 4, (uint64)(v) >> 32))
+#  endif
+#  define STORE64(p, v) STORE64_B((p), (v))
+#  define STORE64_B_(p, v) STORE64_B((p), (v).i)
+#  define STORE64_L_(p, v) STORE64_L((p), (v).i)
+#  define STORE64_(p, v) STORE64_B_((p), (v))
+
+#else
+
+#  define LOAD64_B_(d, p)                                              \
+       ((d).hi = LOAD32_B((octet *)(p) + 0),                           \
+        (d).lo = LOAD32_B((octet *)(p) + 4))
+#  define LOAD64_L_(d, p)                                              \
+       ((d).lo = LOAD32_L((octet *)(p) + 0),                           \
+        (d).hi = LOAD32_L((octet *)(p) + 4))
+#  define LOAD64_(d, p) LOAD64_B_((d), (p))
+
+#  define STORE64_B_(p, v)                                             \
+       (STORE32_B((octet *)(p) + 0, (v).hi),                           \
+        STORE32_B((octet *)(p) + 4, (v).lo))
+#  define STORE64_L_(p, v)                                             \
+       (STORE32_L((octet *)(p) + 0, (v).lo),                           \
+        STORE32_L((octet *)(p) + 4, (v).hi))
+#  define STORE64_(p, v) STORE64_B_((p), (v))
+
+#endif
+
+/* --- Other operations on 64-bit integers --- */
+
+#ifdef HAVE_UINT64
+#  define SET64(d, h, l) ((d).i = (U64((h)) << 32) | U64((l)))
+#  define ASSIGN64(d, x) ((d).i = U64((x)))
+#  define HI64(x) U32((x).i >> 32)
+#  define LO64(x) U32((x).i)
+#  define GET64(t, x) ((t)(x).i)
+#else
+#  define SET64(d, h, l) ((d).hi = U32(h), (d).lo = U32(l))
+#  define ASSIGN64(d, x)                                               \
+    ((d).hi = ((x & ~MASK32) >> 16) >> 16, (d).lo = U32(x))
+#  define HI64(x) U32((x).hi)
+#  define LO64(x) U32((x).lo)
+#  define GET64(t, x) (((((t)HI64(x) << 16) << 16) & ~MASK32) | (t)LO64(x))
+#endif
+
+#ifdef HAVE_UINT64
+#  define AND64(d, x, y) ((d).i = (x).i & (y).i)
+#  define OR64(d, x, y) ((d).i = (x).i | (y).i)
+#  define XOR64(d, x, y) ((d).i = (x).i ^ (y).i)
+#  define CPL64(d, x) ((d).i = ~(x).i)
+#  define ADD64(d, x, y) ((d).i = (x).i + (y).i)
+#  define SUB64(d, x, y) ((d).i = (x).i - (y).i)
+#  define CMP64(x, op, y) ((x).i op (y).i)
+#  define ZERO64(x) ((x) == 0)
+#else
+#  define AND64(d, x, y) ((d).lo = (x).lo & (y).lo, (d).hi = (x).hi & (y).hi)
+#  define OR64(d, x, y) ((d).lo = (x).lo | (y).lo, (d).hi = (x).hi | (y).hi)
+#  define XOR64(d, x, y) ((d).lo = (x).lo ^ (y).lo, (d).hi = (x).hi ^ (y).hi)
+#  define CPL64(d, x) ((d).lo = ~(x).lo, (d).hi = ~(x).hi)
+#  define ADD64(d, x, y) do {                                          \
+     uint32 _x = U32((x).lo + (y).lo);                                 \
+     (d).hi = (x).hi + (y).hi + (_x < (x).lo);                         \
+     (d).lo = _x;                                                      \
+   } while (0)
+#  define SUB64(d, x, y) do {                                          \
+     uint32 _x = U32((x).lo - (y).lo);                                 \
+     (d).hi = (x).hi - (y).hi - (_x > (x).lo);                         \
+     (d).lo = _x;                                                      \
+   } while (0)
+#  define CMP64(x, op, y)                                              \
+    ((x).hi == (y).hi ? (x).lo op (y).lo : (x).hi op (y).hi)
+#  define ZERO64(x) ((x).lo == 0 && (x).hi == 0)
+#endif
+
+/* --- Storing integers in tables --- */
+
+#ifdef HAVE_UINT64
+#  define X64(x, y) { 0x##x##y }
+#else
+#  define X64(x, y) { 0x##x, 0x##y }
+#endif
+
+/*----- That's all, folks -------------------------------------------------*/
+
+#ifdef __cplusplus
+  }
+#endif
+
+#endif
diff --git a/utils/compiler.3 b/utils/compiler.3
new file mode 100644 (file)
index 0000000..1920be4
--- /dev/null
@@ -0,0 +1,35 @@
+.\" -*-nroff-*-
+.TH compiler 3 "26 May 2018" "Straylight/Edgeware" "mLib utilities library"
+.SH NAME
+compiler \- detect compiler version
+.\" @GCC_VERSION_P
+.\" @CLANG_VERSION_P
+.SH SYNOPSIS
+.nf
+.B "#include <mLib/compiler.h>"
+
+.BI "int GCC_VERSION_P(" maj ", " min ");"
+.BI "int CLANG_VERSION_P(" maj ", " min ");"
+.fi
+.SH DESCRIPTION
+The macro invocation
+.BI GCC_VERSION_P( maj ", " min )
+expands to a compile-time constant nonzero value if the present compiler
+is GCC version
+.IR maj . min
+or better, or claims compatibility with it.
+This is frequently imperfect, as many compilers claim compatibility
+without implementing all of the necessary features, but it works
+adequately if one takes care.
+.PP
+The macro invocation
+.BI CLANG_VERSION_P( maj ", " min )
+expands to a compile-time constant nonzero value if the present compiler
+is Clang version
+.IR maj . min
+or better (or claims compatibility with it, but this is less likely
+than for GCC.
+.SH "SEE ALSO"
+.BR mLib (3).
+.SH "AUTHOR"
+Mark Wooding, <mdw@distorted.org.uk>
diff --git a/utils/compiler.h b/utils/compiler.h
new file mode 100644 (file)
index 0000000..edc6e4d
--- /dev/null
@@ -0,0 +1,58 @@
+/* -*-c-*-
+ *
+ * Macros for determining the compiler version
+ *
+ * (c) 2018 Straylight/Edgeware
+ */
+
+/*----- Licensing notice --------------------------------------------------*
+ *
+ * This file is part of the mLib utilities library.
+ *
+ * mLib is free software: you can redistribute it and/or modify it under
+ * the terms of the GNU Library General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or (at
+ * your option) any later version.
+ *
+ * mLib is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Library General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with mLib.  If not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
+ * USA.
+ */
+
+#ifndef MLIB_COMPILER_H
+#define MLIB_COMPILER_H
+
+#ifdef __cplusplus
+  extern "C" {
+#endif
+
+/*----- Macros ------------------------------------------------------------*/
+
+#ifdef __GNUC__
+#  define GCC_VERSION_P(maj, min)                                      \
+       (__GNUC__ > (maj) || (__GNUC__ == (maj) && __GNUC_MINOR__ >= (min)))
+#else
+#  define GCC_VERSION_P(maj, min) 0
+#endif
+
+#ifdef __clang__
+#  define CLANG_VERSION_P(maj, min)                                    \
+       (__clang_major__ > (maj) || (__clang_major__ == (maj) &&        \
+                                    __clang_minor__ >= (min)))
+#else
+#  define CLANG_VERSION_P(maj, min) 0
+#endif
+
+/*----- That's all, folks -------------------------------------------------*/
+
+#ifdef __cplusplus
+  }
+#endif
+
+#endif
diff --git a/utils/exc.3 b/utils/exc.3
new file mode 100644 (file)
index 0000000..d4c7eb9
--- /dev/null
@@ -0,0 +1,267 @@
+.\" -*-nroff-*-
+.de VS
+.sp 1
+.in +5n
+.ft B
+.nf
+..
+.de VE
+.ft R
+.in -5n
+.sp 1
+.fi
+..
+.TH exc 3 "20 June 1999" "Straylight/Edgeware" "mLib utilities library"
+.SH NAME
+exc \- exception handling for C programs
+.\" @TRY
+.\" @CATCH
+.\" @END_TRY
+.\" @THROW
+.\" @RETHROW
+.\"
+.\" @exc_uncaught
+.\"
+.\" @EXC_PAIR
+.\" @EXC_ALLOC
+.\" @EXC_ALLOCN
+.\" @EXC_ALLOCI
+.\" @EXC_ALLOCP
+.\" @EXC_ALLOCS
+.\"
+.SH SYNOPSIS
+.B "#include <mLib/exc.h>"
+.sp 1
+.B TRY
+.I statement
+.B CATCH
+.I statement
+.B END_TRY;
+.br
+.B EXIT_TRY;
+.sp 1
+.BI "THROW(exc_extype " type
+.RB [ ,
+.IR data ]\fB);
+.br
+.B RETHROW;
+.sp 1
+.nf
+.B "typedef void (*exc__uncaught)(exc_extype, exc_exval);"
+.BI "exc__uncaught exc_uncaught(exc__uncaught " proc );
+
+.BI "exc_extype EXC_PAIR(unsigned char " x ", unsigned char " y );
+.BI "exc_extype EXC_ALLOC(exc_extype " owner ", exc_extype " type );
+.BI "exc_extype EXC_ALLOCN(exc_extype " owner ", exc_extype " type );
+.BI "exc_extype EXC_ALLOCI(exc_extype " owner ", exc_extype " type );
+.BI "exc_extype EXC_ALLOCP(exc_extype " owner ", exc_extype " type );
+.BI "exc_extype EXC_ALLOCS(exc_extype " owner ", exc_extype " type );
+.fi
+.SH DESCRIPTION
+The header file
+.B <mLib/exc.h>
+introduces some new syntax and definitions to support exception handling
+in C.  The marriage is not particularly successful, although it works
+well enough in practice.
+.PP
+The syntax introduced consists of new
+.B TRY
+and
+.B EXIT_TRY
+statements and a pair of new expression types
+.B THROW
+and
+.BR RETHROW .
+It's unfortunately important to remember that the syntax is provided
+using macro expansion and standard C facilities; some of the
+restrictions of these features show through.
+.SS "The TRY statement"
+The
+.B TRY
+statement associates an exception handler with a piece of code.  The
+second statement is an
+.IR "exception handler" .
+Its
+.I "dynamic scope"
+is the duration of the first statement's execution, together with the
+duration of any functions called within the dynamic scope.  (Note in
+particular that an exception handler is not within its own dynamic
+scope.)  A thrown exception causes the exception handler with
+dynamically innermost scope to be executed.
+.PP
+Two special variables are provided to the exception handler:
+.TP
+.B exc_type
+The
+.I type
+of the exception caught.  This is value of type
+.B exc_extype
+(described below).
+.TP
+.B exc_val
+The
+.I value
+of the exception.  This has a union type, with members
+.BR "int i",
+.B "void *p"
+and
+.BR "char *s" .
+Only one of the members is valid; you should be able to work out which
+from the exception type.  There are abbreviations
+.BR "exc_i",
+.B exc_p
+and
+.B exc_s
+which refer to members of
+.B exc_val
+directly.
+.SS "The EXIT_TRY statement"
+It is not safe to leave the dynamic scope of an exception handler early
+(e.g., by a
+.B goto
+statement).  You can force a safe exit from a dynamic scope using the
+.B EXIT_TRY
+statement from within the
+.I lexical
+scope of the
+.B TRY
+statement.
+.SS "The THROW and RETHROW statements"
+The
+.B THROW
+expression throws an exception.  The first argument is the type of
+exception; the second is some data to attach to the exception.  The type
+of data, integer, string or pointer, is determined from the exception
+type.
+.PP
+Control is immediately passed to the exception handler with the
+innermost enclosing dynamic scope.
+.PP
+The
+.B RETHROW
+expression is only valid within an exception handler.  It rethrows the
+last exception caught by the handler.
+.PP
+Neither
+.B THROW
+nor
+.B RETHROW
+yields any value.
+.SS "Exception type allocation"
+Exception types are 32-bit values.  The top 16 bits are an
+.IR "owner identifier" .
+The idea is that each library can have an owner identifier, and it can
+then allocate exceptions for its own use from the remaining space.  Two
+special owner codes are defined:
+.TP
+.B "EXC_GLOBAL (0x0000)"
+The global space defined for everyone's benefit.  Don't define your own
+exceptions in this space.
+.TP
+.B "EXC_SHARED (0xffff)"
+A shared space.  You can use this for any exceptions which won't be seen
+by anyone else.
+.PP
+Other owner codes may be allocated by choosing two characters (probably
+letters) which best suit your application and applying the
+.B EXC_PAIR
+macro to them.  For example, the owner code for
+.B mLib
+would probably be
+.BR "EXC_PAIR('m', 'L')" ,
+if
+.B mLib
+defined any exceptions other then the global ones.
+.PP
+The bottom 16 bits are the actual exception type, and the data type
+which gets passed around with the exception.  The data type is
+(bizarrely) in bits 6 and 7 of the type word.  The data type code is one
+of the following:
+.TP
+.B EXC_NOVAL
+There is no data associated with this exception.
+.TP
+.B EXC_INTVAL
+The data is an integer, with type
+.BR int .
+.TP
+.B EXC_PTRVAL
+The data is a pointer to some data structure, with type
+.BR "void *" .
+Note that you probably have to do an explicit cast to
+.B "void *"
+in the
+.B THROW
+expression.
+.TP
+.B EXC_STRVAL
+The data is a pointer to a string of characters, of type
+.BR "char *" .
+.PP
+If the data to be thrown is a pointer, make sure that the object pointed
+to has a long enough lifetime for it to actually get to its exception
+handler intact.  In particular, don't pass pointers to automatic
+variables unless you're
+.I sure
+they were allocated outside the handler's dynamic scope.
+.PP
+Individual exceptions are allocated by the macros
+.BI EXC_ALLOC t\fR,
+where
+.I t
+is one of:
+.TP
+.B N
+The exception has no data
+.TP
+.B I
+The exception data is an integer.
+.TP
+.B P
+The exception data is a pointer.
+.TP
+.B S
+The exception data is a character string.
+.PP
+The
+.BI EXC_ALLOC t
+macros take two arguments: the owner code (usually allocated with
+.B EXC_PAIR
+as described above), and the type code.  The data type is encoded into
+the exception type by the allocation macro.
+.SS "Predefined exceptions"
+The following exceptions are predefined:
+.TP
+.B EXC_NOMEM
+No data.  Signals an out-of-memory condition.
+.TP
+.B EXC_ERRNO
+Integer data.  Signals an operating system error.  The data is the value
+of
+.B errno
+associated with the error.
+.TP
+.B EXC_OSERROR
+Pointer data.  Signals a RISC\ OS error.  The data is a pointer to the
+RISC\ OS error block.  (Non RISC\ OS programmers don't need to worry
+about this.)
+.TP
+.B EXC_SIGNAL
+Integer data.  Signals a raised operating system signal.  The data is
+the signal number.
+.TP
+.B EXC_FAIL
+String data.  Signals a miscellaneous failure.  The data is a pointer to
+an explanatory string.
+.SH BUGS
+The call to an exception handler is achieved using
+.BR longjmp (3).
+Therefore all the caveats about
+.B longjmp
+and automatic data apply.  Also, note that status such as the signal
+mask is not reset, so you might have to do that manually in order to
+recover from a signal.
+.SH "SEE ALSO"
+.BR mLib (3).
+.SH AUTHOR
+Mark Wooding, <mdw@distorted.org.uk>
diff --git a/utils/exc.c b/utils/exc.c
new file mode 100644 (file)
index 0000000..9cc0306
--- /dev/null
@@ -0,0 +1,136 @@
+/* -*-c-*-
+ *
+ * Structured exception handling in C
+ *
+ * (c) 1998 Straylight/Edgeware
+ */
+
+/*----- Licensing notice --------------------------------------------------*
+ *
+ * This file is part of the mLib utilities library.
+ *
+ * mLib is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * mLib is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with mLib; if not, write to the Free
+ * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+ * MA 02111-1307, USA.
+ */
+
+#include <stdarg.h>
+#include <stdlib.h>
+#include <stdio.h>
+
+#include "exc.h"
+
+/*----- Global variables --------------------------------------------------*/
+
+__exc_hnd *__exc_list = 0;
+
+/*----- Functions ---------------------------------------------------------*/
+
+/* --- @duff@ --- *
+ *
+ * Arguments:  @exc_extype type@ = type of duff exception
+ *             @exc_exval val@ = extra data supplied
+ *
+ * Returns:    Doesn't
+ *
+ * Use:                Default handler when everything goes wrong.
+ */
+
+static void duff(exc_extype type, exc_exval val)
+{
+  fprintf(stderr, "fatal error: uncaught exception (type = %lx)\n", type);
+  abort();
+}
+
+/* --- @duffproc@ --- *
+ *
+ * Current handler when there are no more exceptions left.
+ */
+
+static exc__uncaught duffproc = duff;
+
+/* --- @exc_uncaught@ --- *
+ *
+ * Arguments:  @void (*proc)(exc_extype type, exc_exval val) = new handler
+ *
+ * Returns:    Pointer to the old handler value.
+ *
+ * Use:                Sets the handler for uncaught exceptions.
+ */
+
+exc__uncaught exc_uncaught(exc__uncaught proc)
+{
+  exc__uncaught p = duffproc;
+  if (proc)
+    duffproc = proc;
+  return (p);
+}
+
+/* --- @__exc_throw@ --- *
+ *
+ * Arguments:  @exc_extype type@ = type of exception to throw
+ *
+ * Returns:    Doesn't
+ *
+ * Use:                NOT FOR USER CONSUMPTION.  Reads an appropriate exception
+ *             value and throws an exception.
+ */
+
+void __exc_throw(exc_extype type, ...)
+{
+  va_list ap;
+  exc_exval v;
+
+  va_start(ap, type);
+  switch (type & 0xC0) {
+    case EXC_NOVAL:
+      v.i = 0;
+      break;
+    case EXC_INTVAL:
+      v.i = va_arg(ap, int);
+      break;
+    case EXC_PTRVAL:
+      v.p = va_arg(ap, void *);
+      break;
+    case EXC_STRVAL:
+      v.s = va_arg(ap, char *);
+      break;
+  }
+  va_end(ap);
+  __exc_rethrow(type, v);
+}
+
+/* --- @__exc_rethrow@ --- *
+ *
+ * Arguments:  @exc_extype type@ = type of exception to throw
+ *             @exc_exval val@ = value of exception to throw
+ *
+ * Returns:    Doesn't
+ *
+ * Use:                NOT FOR USER CONSUMPTION.  Does the donkey-work of raising
+ *             an exception.
+ */
+
+void __exc_rethrow(exc_extype type, exc_exval val)
+{
+  __exc_hnd *p = __exc_list;
+  if (!p)
+    duffproc(type, val);
+  p->type = type;
+  p->val = val;
+  __exc_list = p->next;
+  longjmp(p->buf, type);
+}
+
+/*----- That's all, folks -------------------------------------------------*/
diff --git a/utils/exc.h b/utils/exc.h
new file mode 100644 (file)
index 0000000..cd3f1c5
--- /dev/null
@@ -0,0 +1,333 @@
+/* -*-c-*-
+ *
+ * Structured exception handling in C
+ *
+ * (c) 1998 Straylight/Edgeware
+ */
+
+/*----- Licensing notice --------------------------------------------------*
+ *
+ * This file is part of the mLib utilities library.
+ *
+ * mLib is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * mLib is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with mLib; if not, write to the Free
+ * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+ * MA 02111-1307, USA.
+ */
+
+#ifndef MLIB_EXC_H
+#define MLIB_EXC_H
+
+#ifdef __cplusplus
+  extern "C" {
+#endif
+
+#include <setjmp.h>
+
+/*----- Quick documentation -----------------------------------------------*
+ *
+ * This header file provides some exception handling facilities in C
+ * programs.  It modifies the syntax of the language slightly, using the
+ * preprocessor.
+ *
+ * The `throw' expression returns no value.  It has the syntax:
+ *
+ *   THROW ( expr , expr )
+ *
+ * The first expression must have type compatible with unsigned integer; it
+ * identifies an `exception type'.  The second must have type compatible
+ * with pointer to void; it contains the `exception data'.  Control is
+ * passed to the current exception handler.
+ *
+ * The `RETHROW' expression, valid only within an exception handler, causes
+ * the current exception to be thrown again.
+ *
+ * A `try' statement has the syntax:
+ *
+ *   TRY stat CATCH stat END_TRY;
+ *
+ * The first statement is called the `test'; the second is the `handler'.
+ * During execution of the test, the handler is added to a stack of
+ * active exception handlers; the topmost handler on this stack is called
+ * the `current' handler.  When execution of the test completes, the
+ * corresponding handler is removed from the stack.
+ *
+ * The test statement may complete in one of these ways:
+ *
+ *   * Normal completion -- control reaches the end of the statement
+ *     normally.
+ *
+ *   * Throwing an exception -- an exception is thrown when the handler is
+ *     the current exception handler.
+ *
+ *   * By executing a `break' statement.
+ *
+ *   * By executing the expression `EXIT_TRY' and transferring control to
+ *     a point outside the entire `try' statement (e.g., executing a `goto'
+ *     or `return' statement).
+ *
+ * Any other attempt to leave the test causes undefined behaviour.
+ *
+ * If an exception is thrown while the handler is the current exception
+ * handler, it is given control.  The variables `exc_type' and `exc_val'
+ * denote the exception type and value respectively -- they are passed
+ * unchanged from the `throw' expression which caused the exception.
+ * A handler is deactivated before it is invoked; if it causes an
+ * exception to be thrown (and does not contain a nested `try' statement)
+ * control will be passed to an earlier active handler.
+ *
+ * Control is passed to handlers using the `longjmp' function.
+ *
+ * Example:
+ *
+ *   TRY {
+ *     ... something dangerous ...
+ *   } CATCH switch (exc_type) {
+ *     case EXC_INTERESTING:
+ *      ... handle exception ...
+ *      break;
+ *     default:
+ *      ... do tidying up ...
+ *      RETHROW;
+ *   } END_TRY;
+ */
+
+/*----- Exception type allocation -----------------------------------------*
+ *
+ * Nobody allocates exception types, so we'll just have to try to get along
+ * without too many collisions.  An exception type is an unsigned long,
+ * which gives us four bytes.  The top two bytes identify the library which
+ * `owns' the exception, with special values zero meaning `defined as part
+ * of the system' and 0xFFFF providing a shared space of types which can
+ * be used by anyone as long as they don't get seen by anyone else.
+ *
+ * The lower byte pair encodes a type number, and a value which defines
+ * the type of the value field (see below).
+ */
+
+/* --- Type of an exception --- */
+
+typedef unsigned long exc_extype;
+
+/* --- Build a byte pair from two characters --- *
+ *
+ * Note the icky casting to handle signed chars.
+ */
+
+#define EXC_PAIR(x, y) (((unsigned long)(unsigned char)(x) << 8) |     \
+                       (unsigned long)(unsigned char)(y))
+
+/* --- Allocate an exception number --- */
+
+#define EXC_ALLOC(owner, type) (((unsigned long)(owner) << 16) |       \
+                               (unsigned long)(type))
+
+/* --- Special owner codes --- */
+
+#define EXC_GLOBAL 0u                  /* The global space defined here */
+#define EXC_SHARED 0xFFFFu             /* The shared space for everyone */
+#define EXC_MLIB EXC_PAIR('m', 'L')    /* Space for mLib exceptions */
+
+/*----- Exception values --------------------------------------------------*
+ *
+ * Exception values can have several different types.  This is a mess, and
+ * C doesn't handle it too well, but we can try.  I'll encode the value type
+ * as part of the exception type, in the top bits of the bottom byte.  Messy?
+ * You betcha.
+ */
+
+/* --- Encoding a value type in an extype --- */
+
+#define EXC_TYPECODE(t, w) (((w) & ~0xC0u) | ((t) & 0xC0u))
+
+/* --- The various value types --- */
+
+#define EXC_NOVAL 0x00u                        /* No interesting value */
+#define EXC_INTVAL 0x40u               /* Integer value */
+#define EXC_PTRVAL 0x80u               /* Arbitrary pointer value */
+#define EXC_STRVAL 0xC0u               /* Pointer to character string */
+
+/* --- Allocating exceptions with appropriate types --- */
+
+#define EXC_ALLOCN(o, t) EXC_TYPECODE(EXC_NOVAL,  EXC_ALLOC(o, t))
+#define EXC_ALLOCI(o, t) EXC_TYPECODE(EXC_INTVAL, EXC_ALLOC(o, t))
+#define EXC_ALLOCP(o, t) EXC_TYPECODE(EXC_PTRVAL, EXC_ALLOC(o, t))
+#define EXC_ALLOCS(o, t) EXC_TYPECODE(EXC_STRVAL, EXC_ALLOC(o, t))
+
+/* --- A union representing the type --- */
+
+typedef union exc_exval {
+  int i;
+  void *p;
+  char *s;
+} exc_exval;
+
+/*----- Predefined exceptions ---------------------------------------------*/
+
+/* --- @EXC_NOMEM@ --- *
+ *
+ * Value:      ---
+ *
+ * Meaning:    An attempt to allocate memory failed.
+ */
+
+#define EXC_NOMEM EXC_ALLOCN(EXC_GLOBAL, 0u)
+
+/* --- @EXC_ERRNO@ --- *
+ *
+ * Value:      @int errno@ = the error raised
+ *
+ * Meaning:    Some kind of OS error occurred.
+ */
+
+#define EXC_ERRNO EXC_ALLOCI(EXC_GLOBAL, 1u)
+
+/* --- @EXC_OSERROR@ --- *
+ *
+ * Value:      @os_error *e@ = pointer to error block
+ *
+ * Meaning:    For RISC OS programmers only: alternative way of propagating
+ *             errors.
+ */
+
+#define EXC_OSERROR EXC_ALLOCP(EXC_GLOBAL, 1u)
+
+/* --- @EXC_SIGNAL@ --- *
+ *
+ * Value:      @int sig@ = signal number
+ *
+ * Meaning:    Report the raising of a signal.
+ */
+
+#define EXC_SIGNAL EXC_ALLOCI(EXC_GLOBAL, 2u)
+
+/* --- @EXC_FAIL@ --- *
+ *
+ * Value:      @const char *p@ = pointer to expanatory string
+ *
+ * Meaning:    Miscellaneous error.
+ */
+
+#define EXC_FAIL EXC_ALLOCS(EXC_GLOBAL, 0xFFu)
+
+/*----- An exception handler block ----------------------------------------*/
+
+/* --- Try to think of this as being opaque --- */
+
+typedef struct __exc_hnd {
+  struct __exc_hnd *next;              /* Pointer to next record down */
+  exc_extype type;                     /* Type of this exception */
+  exc_exval val;                       /* Value of this exception */
+  jmp_buf buf;                         /* Jump buffer when exceptions hit */
+} __exc_hnd;
+
+/*----- Global variables --------------------------------------------------*/
+
+extern __exc_hnd *__exc_list;          /* List of active handlers */
+
+/*----- Macros ------------------------------------------------------------*/
+
+/* --- References to current exception type and value --- */
+
+#define exc_type (__exc_ec.type)
+#define exc_val (__exc_ec.val)
+#define exc_i (__exc_ec.val.i)
+#define exc_p (__exc_ec.val.p)
+#define exc_s (__exc_ec.val.s)
+
+/* --- How it actually works --- *
+ *
+ * A `try' block is contained within a block which provides an exception
+ * handler buffer in automatic storage.  This block is a loop, to allow
+ * `break' to escape from it.  It adds the handler buffer to the top of a
+ * list, and does a `setjmp' to allow a return here following an exception.
+ * The `setjmp' returns zero for the `try' section, and nonzero if there's
+ * an exception to `catch'.  It looks a little like this:
+ *
+ *   do {
+ *     __exc_hnd h;
+ *     add_handler(&h);
+ *     if (!setjmp(h.buf)) {
+ *      do <try code> while (0);
+ *      remove_handler(&h);
+ *     } else
+ *      <catch code>
+ *   } while (0)
+ *
+ * Everything else is ugly hacking to make things work.
+ */
+
+/* --- Trying things which may cause exceptions --- */
+
+#define TRY do {                                                       \
+  volatile __exc_hnd __exc_ec;                                         \
+  __exc_ec.next = __exc_list;                                          \
+  __exc_list = (__exc_hnd *)&__exc_ec;                                 \
+  if (!setjmp(*(jmp_buf *)&__exc_ec.buf /* very nasty! */ )) { do
+
+#define EXIT_TRY do __exc_list = __exc_ec.next; while (0)
+#define CATCH while (0); EXIT_TRY; } else
+
+#define END_TRY } while (0)
+
+/* --- Raising exceptions --- */
+
+#define THROW __exc_throw
+#define RETHROW __exc_rethrow(__exc_ec.type, __exc_ec.val)
+
+/*----- Functions ---------------------------------------------------------*/
+
+/* --- @exc_uncaught@ --- *
+ *
+ * Arguments:  @void (*proc)(exc_extype type, exc_exval val) = new handler
+ *
+ * Returns:    Pointer to the old handler value.
+ *
+ * Use:                Sets the handler for uncaught exceptions.
+ */
+
+typedef void (*exc__uncaught)(exc_extype /*type*/, exc_exval /*val*/);
+extern exc__uncaught exc_uncaught(exc__uncaught /*proc*/);
+
+/* --- @__exc_throw@ --- *
+ *
+ * Arguments:  @exc_extype type@ = type of exception to throw
+ *
+ * Returns:    Doesn't
+ *
+ * Use:                NOT FOR USER CONSUMPTION.  Reads an appropriate exception
+ *             value and throws an exception.
+ */
+
+extern void __exc_throw(exc_extype /*type*/, ...);
+
+/* --- @__exc_rethrow@ --- *
+ *
+ * Arguments:  @exc_extype type@ = type of exception to throw
+ *             @exc_exval val@ = value of exception to throw
+ *
+ * Returns:    Doesn't
+ *
+ * Use:                NOT FOR USER CONSUMPTION.  Does the donkey-work of raising
+ *             an exception.
+ */
+
+extern void __exc_rethrow(exc_extype /*type*/, exc_exval /*val*/);
+
+/*----- That's all, folks -------------------------------------------------*/
+
+#ifdef __cplusplus
+  }
+#endif
+
+#endif
diff --git a/utils/macros.3 b/utils/macros.3
new file mode 100644 (file)
index 0000000..d481a47
--- /dev/null
@@ -0,0 +1,277 @@
+.\" -*-nroff-*-
+.TH macros 3 "13 December 2003" "Straylight/Edgeware" "mLib utilities library"
+.SH NAME
+macros \- useful macros
+.\" @N
+.\" @STR
+.\" @GLUE
+.\" @STATIC_ASSERT
+.\" @ISALNUM
+.\" @ISALPHA
+.\" @ISASCII
+.\" @ISBLANK
+.\" @ISCNTRL
+.\" @ISDIGIT
+.\" @ISGRAPH
+.\" @ISLOWER
+.\" @ISPRINT
+.\" @ISPUNCT
+.\" @ISSPACE
+.\" @ISUPPER
+.\" @ISXDIGIT
+.\" @TOASCII
+.\" @TOLOWER
+.\" @TOUPPER
+.\" @MEMCMP
+.\" @STRCMP
+.\" @STRNCMP
+.\" @DISCARD
+.\" @IGNORE
+.\" @DEPRECATED
+.\" @EXECL_LIKE
+.\" @IGNORABLE
+.\" @MUST_CHECK
+.\" @NORETURN
+.\" @PRINTF_LIKE
+.\" @SCANF_LIKE
+.\" @MUFFLE_WARNINGS_DECL
+.\" @MUFFLE_WARNINGS_EXPR
+.\" @MUFFLE_WARNINGS_STMT
+.\" @GCC_WARNING
+.\" @CLANG_WARNING
+.SH SYNOPSIS
+.nf
+.B "#include <mLib/macros.h>"
+
+.BI "size_t N(" array ");"
+.BI "STR(" tokens\fR... ")"
+.BI "GLUE(" tokens\fR... ", " tokens\fR... ")"
+.BI "STATIC_ASSERT(" cond ", " msg ");"
+
+.BI "ISALNUM(int " ch ");"
+.BI "ISALPHA(int " ch ");"
+.BI "ISASCII(int " ch ");"
+.BI "ISBLANK(int " ch ");"
+.BI "ISCNTRL(int " ch ");"
+.BI "ISDIGIT(int " ch ");"
+.BI "ISGRAPH(int " ch ");"
+.BI "ISLOWER(int " ch ");"
+.BI "ISPRINT(int " ch ");"
+.BI "ISPUNCT(int " ch ");"
+.BI "ISSPACE(int " ch ");"
+.BI "ISUPPER(int " ch ");"
+.BI "ISXDIGIT(int " ch ");"
+.BI "TOASCII(int " ch ");"
+.BI "TOLOWER(int " ch ");"
+.BI "TOUPPER(int " ch ");"
+
+.BI "MEMCMP(const void *" x ", " op ", const void *" y ", size_t " n ");"
+.BI "STRCMP(const char *" x ", " op ", const char *" y ");"
+.BI "STRNCMP(const char *" x ", " op ", const char *" y ", size_t " n ");"
+
+.BI "void DISCARD(" scalar ");"
+.BI "void IGNORE(" variable ");"
+
+.BI "DEPRECATED(" msg ")"
+.BI "EXECL_LIKE(" ntrail ")"
+.BI "IGNORABLE"
+.BI "MUST_CHECK"
+.BI "NORETURN"
+.BI "PRINTF_LIKE(" fmt-index ", " arg-index ")"
+.BI "SCANF_LIKE(" fmt-index ", " arg-index ")"
+
+.BI "MUFFLE_WARNINGS_DECL(" warns ", " decls ")"
+.BI "MUFFLE_WARNINGS_EXPR(" warns ", " expr ")"
+.BI "MUFFLE_WARNINGS_STMT(" warns ", " stmt ")"
+
+.BI "GCC_WARNING(" option ")"
+.BI "CLANG_WARNING(" option ")"
+.fi
+.SH DESCRIPTION
+.SS Utilities
+The
+.B N
+macro returns the number of elements in the named
+.IR array .
+.PP
+The
+.B STR
+macro expands to a string literal containing the result of expanding its
+argument
+.IR tokens .
+.PP
+The
+.B GLUE
+macro expands to a single token, which is the result of gluing together
+the tokens resulting from expanding its argument token lists.  Each of
+the argument token lists must expand to a single preprocessing token,
+and the result of gluing these tokens together must be valid
+preprocessing token.
+.PP
+The
+.B STATIC_ASSERT
+causes compilation to fail if the integer constant expression
+.I cond
+evaluates to zero.  This macro uses the C11
+.B static_assert
+declaration if available, and the
+.I msg
+will be reported in the compiler's diagnostic messsage; otherwise, the macro
+falls back to a somewhat ugly hack which currently ignores the
+.IR msg .
+.PP
+The
+.BR IS ...\&
+and
+.BR TO ...\&
+macros are wrappers around the corresponding standard
+.B <ctype.h>
+macros with the corresponding lowercase names.  They take care of
+forcing the character argument
+.I ch
+to
+.BR "unsigned char" :
+this conversion is necessary on platforms with signed
+.B char
+to avoid passing negative values into the standard macros.
+.PP
+The
+.BR MEMCMP ,
+.BR STRCMP ,
+and
+.B STRNCMP
+macros are wrappers around the standard
+.B <string.h>
+functions with the corresponding lowercase names.  They take an
+additional argument
+.I op
+which is a equality or ordering operator (e.g.,
+.B ==
+or
+.BR > )
+inserted between the two operands.  The standard functions return a
+false value if and only if the operands are equal, which is
+counterintuitive and leads to mistakes; requiring an explicit relational
+operator should reduce the number of such mistakes.
+.PP
+The
+.B DISCARD
+macro discards its argument, which must be of some scalar type.  This
+can be useful in muffling warnings about ignoring return codes in cases
+where you really don't care.
+.PP
+The
+.B IGNORE
+macro ignores its argument, which may be an expression of any type.
+This can be useful in muffling warnings about unused variables.
+.SS Annotations
+The following annotations can be attached to function declarations and
+definitions, as part of the declaration specifiers.  (Other positions
+may also work, depending on your compiler, but don't bet on it.)  They
+might not have any effect, depending on your specific compiler.
+Currently only GCC is well supported, but exactly which features are
+available depend on the compiler version.
+.PP
+Using a function or variable marked as
+.B DEPRECATED
+may provoke a compiler warning; this warning may (depending on your
+compiler version) include the given
+.IR msg .
+.PP
+A variadic function marked as
+.B EXECL_LIKE
+must be called with a null pointer (i.e., an integer constant
+expression with value 0, cast to
+.BR "void *")
+in the variadic part of its argument list, followed by
+.I ntrail
+further arguments.  Typically,
+.I ntrail
+is zero.  Compilers may warn about misuse of such functions.
+.PP
+A function or variable marked as
+.B IGNORABLE
+need not be used.  This may muffle warnings about leaving the marked
+definition unused.
+.PP
+A function marked as
+.B MUST_CHECK
+returns an important value: a warning may be issued if a caller
+ignores the value.  The return type must not be
+.BR void .
+.PP
+A function marked as
+.B NORETURN
+must not return.  It must have return type
+.BR void .
+This may be useful in muffling warnings about uninitialized variables,
+for example.
+.PP
+A variadic function marked as
+.B PRINTF_LIKE
+takes a
+.BR printf (3)-style
+format argument (in position
+.IR fmt-index ,
+counting from 1) and a variable-length list of arguments to be formatted
+(starting from position
+.IR arg-index ).
+Compilers may warn about misuse of such functions.
+.PP
+A variadic function marked as
+.B SCANF_LIKE
+takes a
+.BR scanf (3)-style
+format argument (in position
+.IR fmt-index ,
+counting from 1) and a variable-length list of arguments to be formatted
+(starting from position
+.IR arg-index ).
+Compilers may warn about misuse of such functions.
+.SS Muffling warnings
+Some compilers allow you to muffle warnings in particular pieces of
+code.  These macros provide a compiler-neutral interface to such
+facilities.  Each macro takes an argument
+.IR warns ,
+which is a sequence of calls to
+.IB compiler _WARNING
+macros listing the warnings to be muffled.  The list may contain
+warnings for several different compilers.  The other argument is a
+.I body
+consisting of declarations (in the case of
+.BR MUFFLE_WARNINGS_DECL ),
+an expression (for
+.BR MUFFLE_WARNINGS_EXPR ),
+or a statement (for
+.BR MUFFLE_WARNINGS_STMT ).
+.SS GCC-specific features
+The
+.B GCC_VERSION_P
+macro returns a nonzero value if the compiler is at least version
+.IB maj . min
+of GCC, and zero otherwise.  It's useful in preprocessor conditions.
+.PP
+The
+.B GCC_WARNING
+macro is intended to be used in
+.I warns
+lists (see above).  It takes a string-literal argument
+.I option
+naming a GCC warning option, e.g.,
+.BR """\-Wdiv-by-zero""" .
+.PP
+The
+.B CLANG_WARNING
+is similar, except that it works with the Clang compiler.
+.PP
+Note that including
+.B <mLib/macros.h>
+also defines the compiler-test macros in
+.BR <mLib/compiler.h>;
+see
+.BR compiler (3).
+.SH "SEE ALSO"
+.BR mLib (3),
+.BR compiler (3).
+.SH "AUTHOR"
+Mark Wooding, <mdw@distorted.org.uk>
diff --git a/utils/macros.h b/utils/macros.h
new file mode 100644 (file)
index 0000000..70f535f
--- /dev/null
@@ -0,0 +1,248 @@
+/* -*-c-*-
+ *
+ * Handy macros
+ *
+ * (c) 2003 Straylight/Edgeware
+ */
+
+/*----- Licensing notice --------------------------------------------------*
+ *
+ * This file is part of the mLib utilities library.
+ *
+ * mLib is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * mLib is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with mLib; if not, write to the Free
+ * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+ * MA 02111-1307, USA.
+ */
+
+#ifndef MLIB_MACROS_H
+#define MLIB_MACROS_H
+
+#ifdef __cplusplus
+  extern "C" {
+#endif
+
+/*----- Header files ------------------------------------------------------*/
+
+#include <assert.h>
+
+#ifndef MLIB_COMPILER_H
+#  include "compiler.h"
+#endif
+
+/*----- Miscellaneous utility macros --------------------------------------*/
+
+#define N(v) (sizeof(v)/sizeof(*(v)))
+
+#define MLIB__STR(x) #x
+#define STR(x) MLIB__STR(x)
+
+#define MLIB__GLUE(x, y) x##y
+#define GLUE(x, y) MLIB__GLUE(x, y)
+
+#ifdef static_assert
+#  define STATIC_ASSERT(cond, msg) static_assert(!!(cond), msg)
+#else
+#  define STATIC_ASSERT(cond, msg)                                     \
+       IGNORABLE extern char static_assert_failed[2*!!(cond) - 1]
+#endif
+
+/*----- String and character hacks ----------------------------------------*/
+
+#define CTYPE_HACK(func, ch) (func((unsigned char)(ch)))
+
+#define ISALNUM(ch) CTYPE_HACK(isalnum, ch)
+#define ISALPHA(ch) CTYPE_HACK(isalpha, ch)
+#define ISASCII(ch) CTYPE_HACK(isascii, ch)
+#define ISBLANK(ch) CTYPE_HACK(isblank, ch)
+#define ISCNTRL(ch) CTYPE_HACK(iscntrl, ch)
+#define ISDIGIT(ch) CTYPE_HACK(isdigit, ch)
+#define ISGRAPH(ch) CTYPE_HACK(isgraph, ch)
+#define ISLOWER(ch) CTYPE_HACK(islower, ch)
+#define ISPRINT(ch) CTYPE_HACK(isprint, ch)
+#define ISPUNCT(ch) CTYPE_HACK(ispunct, ch)
+#define ISSPACE(ch) CTYPE_HACK(isspace, ch)
+#define ISUPPER(ch) CTYPE_HACK(isupper, ch)
+#define ISXDIGIT(ch) CTYPE_HACK(isxdigit, ch)
+
+#define TOASCII(ch) CTYPE_HACK(toascii, ch)
+#define TOLOWER(ch) CTYPE_HACK(tolower, ch)
+#define TOUPPER(ch) CTYPE_HACK(toupper, ch)
+
+#define MEMCMP(x, op, y, n) (memcmp((x), (y), (n)) op 0)
+#define STRCMP(x, op, y) (strcmp((x), (y)) op 0)
+#define STRNCMP(x, op, y, n) (strncmp((x), (y), (n)) op 0)
+
+/*----- Compiler diagnostics ----------------------------------------------*/
+
+/* --- Compiler-specific definitions --- */
+
+#if GCC_VERSION_P(2, 5) || CLANG_VERSION_P(3, 3)
+#  define NORETURN __attribute__((__noreturn__))
+#  define PRINTF_LIKE(fix, aix) __attribute__((__format__(printf, fix, aix)))
+#  define SCANF_LIKE(fix, aix) __attribute__((__format__(scanf, fix, aix)))
+#  define IGNORABLE __attribute__((__unused__))
+#endif
+
+#if GCC_VERSION_P(3, 4) || CLANG_VERSION_P(3, 3)
+#  define MUST_CHECK __attribute__((__warn_unused_result__))
+#endif
+
+#if GCC_VERSION_P(4, 5) || CLANG_VERSION_P(3, 3)
+#  define DEPRECATED(msg) __attribute__((__deprecated__(msg)))
+#elif GCC_VERSION_P(3, 1)
+#  define DEPRECATED(msg) __attribute__((__deprecated__))
+#endif
+
+#if GCC_VERSION_P(4, 0) || CLANG_VERSION_P(3, 3)
+#  define EXECL_LIKE(ntrail) __attribute__((__sentinel__(ntrail)))
+#endif
+
+#if CLANG_VERSION_P(3, 3)
+
+#  define MLIB__PRAGMA_HACK(x) _Pragma(#x)
+#  define MLIB__MUFFLE_WARNINGS(warns, body)                           \
+       _Pragma("clang diagnostic push")                                \
+       warns                                                           \
+       body                                                            \
+       _Pragma("clang diagnostic pop")
+#  define CLANG_WARNING(warn)                                          \
+       MLIB__PRAGMA_HACK(clang diagnostic ignored warn)
+#  define MUFFLE_WARNINGS_DECL(warns, body)                            \
+       MLIB__MUFFLE_WARNINGS(warns, body)
+#  define MUFFLE_WARNINGS_EXPR(warns, body)                            \
+       __extension__ ({ MLIB__MUFFLE_WARNINGS(warns, (body);) })
+#  define MUFFLE_WARNINGS_STMT(warns, body)                            \
+       do { MLIB__MUFFLE_WARNINGS(warns, body) } while (0)
+
+#elif GCC_VERSION_P(4, 6)
+
+   /* --- Diagnostic suppression in GCC: a tale of woe --- *
+    *
+    * This is extremely unpleasant, largely as a result of bugs in the GCC
+    * preprocessor's handling of @_Pragma@.  The fundamental problem is
+    * that it's the preprocessor, and not the compiler proper, which
+    * detects @_Pragma@, emitting @#pragma@ lines into its output; and it
+    * does it during macro expansion, even if the macro is being expanded
+    * during argument collection.  Since arguments are expanded before
+    * replacing the macro's invocation with its body, a pragma in an
+    * argument will be emitted %%\emph{before}%% any pragmata in the body,
+    * even if they appear before the argument in the body -- and even if
+    * the argument doesn't actually appear anywhere at all in the body.
+    *
+    * Another, rather less significant, problem is that @_Pragma@'s
+    * argument is a single string literal, recognized in translation phase
+    * 4, before string-literal concatenation in phase 6, so we must build
+    * pragma bodies as token lists and then stringify them.
+    *
+    * As a result, we need some subterfuge here.  The @MLIB__PRAGMA_HACK@
+    * macro issues a @_Pragma@ on its argument token list, which it
+    * stringifies; this deals with the second problem.  The first is
+    * trickier: we must delay expansion of @MLIB__PRAGMA_HACK@ from the
+    * argument collection phase to the body rescanning phase, and we do
+    * this by splitting the invocations between @GCC_WARNING@ macro calls:
+    * the name is left hanging from the previous call (or from
+    * @MLIB__MUFFLE_WARNINGS@, in the first case) and the body is supplied
+    * by @GCC_WARNING@, which also supplies the next @MLIB__PRAGMA_HACK@.
+    * The remaining problem is to make sure we can dispose of the final
+    * trailing @MLIB__PRAGMA_HACK@ harmlessly, which we do by introducing
+    * an extra argument @emitp@, which may be either @t@ or @nil@; this
+    * dispatches to an appropriate helper macro by means of token-pasting.
+    *
+    * I'm so sorry.
+    */
+
+#  define MLIB__PRAGMA_HACK_t(x) _Pragma(#x)
+#  define MLIB__PRAGMA_HACK_nil(x)
+#  define MLIB__PRAGMA_HACK(emitp, x) MLIB__PRAGMA_HACK_##emitp(x)
+#  define MLIB__MUFFLE_WARNINGS(warns, body)                           \
+       _Pragma("GCC diagnostic push") MLIB__PRAGMA_HACK                \
+       warns                                                           \
+       (nil, nil)                                                      \
+       body                                                            \
+       _Pragma("GCC diagnostic pop")
+#  define GCC_WARNING(warn)                                            \
+       (t, GCC diagnostic ignored warn) MLIB__PRAGMA_HACK
+#  define MUFFLE_WARNINGS_DECL(warns, body)                            \
+       MLIB__MUFFLE_WARNINGS(warns, body)
+#  define MUFFLE_WARNINGS_EXPR(warns, body)                            \
+       __extension__ ({ MLIB__MUFFLE_WARNINGS(warns, (body);) })
+#  define MUFFLE_WARNINGS_STMT(warns, body)                            \
+       do { MLIB__MUFFLE_WARNINGS(warns, body) } while (0)
+#endif
+
+/* --- Fallback definitions, mostly trivial --- */
+
+#ifndef DEPRECATED
+#  define DEPRECATED(msg)
+#endif
+
+#ifndef EXECL_LIKE
+#  define EXECL_LIKE(ntrail)
+#endif
+
+#ifndef DISCARD
+#  define DISCARD(x) do if (x); while (0)
+#endif
+
+#ifndef IGNORE
+#  define IGNORE(x) ((void)(x))
+#endif
+
+#ifndef MUFFLE_WARNINGS_DECL
+#  define MUFFLE_WARNINGS_DECL(warns, body) body
+#endif
+
+#ifndef MUFFLE_WARNINGS_EXPR
+#  define MUFFLE_WARNINGS_EXPR(warns, body) (body)
+#endif
+
+#ifndef MUFFLE_WARNINGS_STMT
+#  define MUFFLE_WARNINGS_STMT(warns, body) do { body } while (0)
+#endif
+
+#ifndef PRINTF_LIKE
+#  define PRINF_LIKE(fmtix, argix)
+#endif
+
+#ifndef SCANF_LIKE
+#  define SCANF_LIKE(fmtix, argix)
+#endif
+
+#ifndef NORETURN
+#  define NORETURN
+#endif
+
+#ifndef IGNORABLE
+#  define IGNORABLE
+#endif
+
+#ifndef MUST_CHECK
+#  define MUST_CHECK
+#endif
+
+#ifndef GCC_WARNING
+#  define GCC_WARNING(warn)
+#endif
+
+#ifndef CLANG_WARNING
+#  define CLANG_WARNING(warn)
+#endif
+
+/*----- That's all, folks -------------------------------------------------*/
+
+#ifdef __cplusplus
+  }
+#endif
+
+#endif
diff --git a/utils/str.3 b/utils/str.3
new file mode 100644 (file)
index 0000000..09a6f24
--- /dev/null
@@ -0,0 +1,211 @@
+.\" -*-nroff-*-
+.de VS
+.sp 1
+.in +5n
+.ft B
+.nf
+..
+.de VE
+.ft R
+.in -5n
+.sp 1
+.fi
+..
+.TH str 3 "20 June 1999" "Straylight/Edgeware" "mLib utilities library"
+.SH NAME
+str \- small string utilities
+.\" @str_qword
+.\" @str_qsplit
+.\" @str_getword
+.\" @str_split
+.\" @str_matchx
+.\" @str_match
+.\" @str_sanitize
+.SH SYNOPSIS
+.nf
+.B "#include <mLib/str.h>"
+
+.BI "char *str_qword(char **" pp ", unsigned " f );
+.BI "size_t str_qsplit(char *" p ", char *" v "[], size_t " c ,
+.BI "                  char **" rest ", unsigned " f );
+.BI "char *str_getword(char **" pp );
+.BI "size_t str_split(char *" p ", char *" v "[], size_t " c ", char **" rest );
+.BI "int str_matchx(const char *" p ", const char *" s ", unsigned " f );
+.BI "int str_match(const char *" p ", const char *" s );
+.BI "void str_sanitize(char *" d ", const char *" p ", size_t " sz );
+.fi
+.SH DESCRIPTION
+The header file
+.B <mLib/str.h>
+contains a few small utility functions for manipulating null-terminated
+strings.
+.PP
+The function
+.B str_qword
+extracts the next whitespace-delimited word from a string.  The
+function's argument,
+.IR pp ,
+is the address of a pointer into the string: this pointer is updated by
+.B str_qword
+so that it can extract the following word on the next call and so on.
+The return value is the address of the next word, appropriately null
+terminated.  A null pointer is returned if the entire remainder of the
+string is whitespace.  Note that
+.B str_qword
+modifies the string as it goes, to null-terminate the individual words.
+If the flag
+.B STRF_QUOTE
+is passed, the single- and double-quote characters may be used to quote
+whitespace within words, and the backslash can escape quote characters
+and whitespace.
+.PP
+The function
+.B str_qsplit
+divides a string into whitespace-separated words.  The arguments are as
+follows:
+.TP
+.BI "char *" p
+The address of the string to split.  The string is modified by having
+null terminators written after each word extracted.
+.TP
+.BI "char *" v []
+The address of an array of pointers to characters.  This array will be
+filled in by
+.BR str_split :
+the first entry will point to the first word extracted from the string,
+and so on.  If there aren't enough words in the string, the remaining
+array elements are filled with null pointers.
+.TP
+.BI "size_t " c
+The maximum number of words to extract; also, the number of elements in
+the array
+.IR v .
+.TP
+.BI "char **" rest
+The address of a pointer in which to store the address of the remainder
+of the string.  Leading whitespace is removed from the remainder before
+storing.  If the remainder string is empty, a null pointer is stored
+instead.  If
+.I rest
+is null, the remainder pointer is discarded.
+.TP
+.BI "unsigned " f
+Flags, as for
+.BR str_qsplit .
+.PP
+The return value of
+.B str_qsplit
+is the number of words extracted from the input string.
+.PP
+The functions
+.B str_getword
+and
+.B str_split
+are veneers over
+.B str_qword
+and
+.B str_qsplit
+respectively; they are equivalent to calls to the latter functions with
+flags words of zero.
+.PP
+The
+.B str_matchx
+function does simple wildcard matching.  The first argument is a
+pattern, which may contain metacharacters:
+.RB ` * '
+matches zero or more arbitrary characters;
+.RB ` ? '
+matches exactly one arbitrary characters; and
+.RB ` [ ... ] '
+matches one of the characters listed.  The backslash
+.RB ` \e '
+escapes the following character.  Within square brackets, the
+hyphen
+.RB ` \- '
+may be used to designate ranges of characters.  If the initial character
+is
+.RB ` ! '
+or
+.RB ` ^ '
+then the sense of the match is reversed.  To literally match a
+.RB ` ] '
+character, list it first; to literally match a
+.RB ` \- '
+character, list it immediately after a range, or at the beginning or end
+of the set.  The return value is nonzero if the pattern
+.I p
+matches the given string
+.IR s ,
+or zero if the pattern doesn't match.  If the flag
+.B STRF_PREFIX
+is passed,
+.B str_matchx
+returns true if it reaches the end of the target string before finding a
+mismatch \(en i.e., if the target string is a prefix of a string which
+might match the pattern.  The function
+.B str_match
+is a convenient wrapper for
+.B str_matchx
+with a zero flags word, which is the normal case.
+.PP
+The function
+.B str_sanitize
+copies at most
+.I sz
+\- 1
+characters from the string
+.I p
+to
+.IR d .
+The result string is null terminated.  Any nonprinting characters in
+.I p
+are replaced by an underscore
+.RB ` _ '
+when written to
+.IR d .
+.SH EXAMPLES
+Given the code
+.VS
+char p[] = " alpha  beta gamma   delta ";
+char *v[3];
+size_t n;
+char *q;
+
+n = str_split(p, v, 3, &q);
+.VE
+following the call to
+.BR str_split ,
+.B n
+will have the value 3,
+.B v[0]
+will point to
+.RB ` alpha ',
+.B v[1]
+will point to
+.RB ` beta ',
+.B v[2]
+will point to
+.RB ` gamma '
+and
+.B rest
+will point to
+.RB ` delta\  '
+(note the trailing space).
+.PP
+Similarly, given the string
+.B """\ alpha\ \ beta\ """
+instead,
+.B n
+will be assigned the value 2,
+.B v[0]
+and
+.B v[1]
+will have the same values as last time, and
+.B v[2]
+and
+.B rest
+will be null.
+.SH "SEE ALSO"
+.BR mLib (3).
+.SH AUTHOR
+Mark Wooding, <mdw@distorted.org.uk>
diff --git a/utils/str.c b/utils/str.c
new file mode 100644 (file)
index 0000000..e759828
--- /dev/null
@@ -0,0 +1,324 @@
+/* -*-c-*-
+ *
+ * Functions for hacking with strings
+ *
+ * (c) 1999 Straylight/Edgeware
+ */
+
+/*----- Licensing notice --------------------------------------------------*
+ *
+ * This file is part of the mLib utilities library.
+ *
+ * mLib is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * mLib is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with mLib; if not, write to the Free
+ * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+ * MA 02111-1307, USA.
+ */
+
+/*----- Header files ------------------------------------------------------*/
+
+#include <ctype.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "macros.h"
+#include "str.h"
+
+/*----- Main code ---------------------------------------------------------*/
+
+/* --- @str_qword@ --- *
+ *
+ * Arguments:  @char **pp@ = address of pointer into string
+ *             @unsigned f@ = various flags
+ *
+ * Returns:    Pointer to the next space-separated possibly-quoted word from
+ *             the string, or null.
+ *
+ * Use:                Fetches the next word from a string.  If the flag
+ *             @STRF_QUOTE@ is set, the `\' character acts as an escape, and
+ *             single and double quotes protect whitespace.
+ */
+
+char *str_qword(char **pp, unsigned f)
+{
+  char *p = *pp, *q, *qq;
+  int st = 0, pst = 0;
+
+  /* --- Preliminaries --- */
+
+  if (!p)
+    return (0);
+  while (ISSPACE(*p))
+    p++;
+  if (!*p) {
+    *pp = 0;
+    return (0);
+  }
+
+  /* --- Main work --- */
+
+  for (q = qq = p; *q; q++) {
+    switch (st) {
+      case '\\':
+       *qq++ = *q;
+       st = pst;
+       break;
+      case '\'':
+      case '\"':
+       if (*q == st)
+         st = pst = 0;
+       else if (*q == '\\')
+         st = '\\';
+       else
+         *qq++ = *q;
+       break;
+      default:
+       if (ISSPACE(*q)) {
+         do q++; while (*q && ISSPACE(*q));
+         goto done;
+       } else if (!(f & STRF_QUOTE))
+         goto stdchar;
+       switch (*q) {
+         case '\\':
+           st = '\\';
+           break;
+         case '\'':
+         case '\"':
+           st = pst = *q;
+           break;
+         default:
+         stdchar:
+           *qq++ = *q;
+           break;
+       }
+    }
+  }
+
+  /* --- Finished --- */
+
+done:
+  *pp = *q ? q : 0;
+  *qq++ = 0;
+  return (p);
+}
+
+/* --- @str_qsplit@ --- *
+ *
+ * Arguments:  @char *p@ = pointer to string
+ *             @char *v[]@ = pointer to array to fill in
+ *             @size_t c@ = count of strings to fill in
+ *             @char **rest@ = where to store the remainder of the string
+ *             @unsigned f@ = flags for @str_qword@
+ *
+ * Returns:    Number of strings filled in.
+ *
+ * Use:                Fills an array with pointers to the individual words of a
+ *             string.  The string is modified in place to contain zero
+ *             bytes at the word boundaries, and the words have leading
+ *             and trailing space stripped off.  No more than @c@ words
+ *             are read; the actual number is returned as the value of the
+ *             function.  Unused slots in the array are populated with
+ *             null bytes.  If there's any string left, the address of the
+ *             remainder is stored in @rest@ (if it's non-null); otherwise
+ *             @rest@ is set to a null pointer.
+ */
+
+size_t str_qsplit(char *p, char *v[], size_t c, char **rest, unsigned f)
+{
+  size_t n = 0;
+  char *q;
+
+  while (c && (q = str_qword(&p, f)) != 0) {
+    *v++ = q;
+    c--;
+    n++;
+  }
+  while (c) {
+    *v++ = 0;
+    c--;
+  }
+  if (rest)
+    *rest = p;
+  return (n);
+}
+
+/* --- @str_getword@ --- *
+ *
+ * Arguments:  @char **pp@ = address of pointer into string
+ *
+ * Returns:    Pointer to the next space-separated word from the string,
+ *             or null.
+ *
+ * Use:                Parses off space-separated words from a string.  This is a
+ *             compatibility veneer over @str_qword@.
+ */
+
+char *str_getword(char **pp) { return (str_qword(pp, 0)); }
+
+/* --- @str_split@ --- *
+ *
+ * Arguments:  @char *p@ = pointer to string
+ *             @char *v[]@ = pointer to array to fill in
+ *             @size_t c@ = count of strings to fill in
+ *             @char **rest@ = where to store the remainder of the string
+ *
+ * Returns:    Number of strings filled in.
+ *
+ * Use:                Fills an array with pointers to the individual words of a
+ *             string.  This is a compatibility veneer over @str_qsplit@.
+ */
+
+size_t str_split(char *p, char *v[], size_t c, char **rest)
+  { return (str_qsplit(p, v, c, rest, 0)); }
+
+/* --- @str_matchx@ --- *
+ *
+ * Arguments:  @const char *p@ = pointer to pattern string
+ *             @const char *s@ = string to compare with
+ *             @unsigned f@ = various flags
+ *
+ * Returns:    Nonzero if the pattern matches the string.
+ *
+ * Use:                Does simple wildcard matching.  This is quite nasty and more
+ *             than a little slow.  Supports metacharacters `*', `?' and
+ *             '['.
+ */
+
+int str_matchx(const char *p, const char *s, unsigned f)
+{
+  for (;;) {
+    char pch = *p++, pche, sch;
+    int sense;
+
+    if ((f & STRF_PREFIX) && !*s)
+      return (1);
+    switch (pch) {
+      case '?':
+       if (!*s)
+         return (0);
+       s++;
+       break;
+      case '*':
+       if (!*p || (f & STRF_PREFIX))
+         return (1);
+       while (*s) {
+         if (str_match(p, s))
+           return (1);
+         s++;
+       }
+       return (0);
+      case '[':
+       if (!*s)
+         return (0);
+       sch = *s++;
+       pch = *p++;
+       sense = 1;
+       if (pch == '^' || pch == '!') {
+         sense = !sense;
+         pch = *p++;
+       }
+       if (pch == ']') {
+         if (*p == '-' && p[1] && p[1] != ']') {
+           pche = p[1];
+           p += 2;
+           if (pch <= sch && sch <= pche)
+             goto class_match;
+         } else if (pch == sch)
+           goto class_match;
+         pch = *p++;
+       }
+       for (;; pch = *p++) {
+         if (!pch || pch == ']')
+           goto class_nomatch;
+         if (*p == '-' && p[1] && p[1] != ']') {
+           pche = p[1];
+           p += 2;
+           if (pch <= sch && sch <= pche)
+             goto class_match;
+         } else if (pch == sch)
+           goto class_match;
+       }
+      class_match:
+       if (!sense)
+         return (0);
+       for (;;) {
+         pch = *p++;
+         if (!pch)
+           return (0);
+         if (pch == ']')
+           break;
+         if (*p == '-' && p[1] && p[1] != ']')
+           p += 2;
+       }
+       break;
+      class_nomatch:
+       if (sense)
+         return (0);
+       break;
+      case '\\':
+       pch = *p++;
+      default:
+       if (pch != *s)
+         return (0);
+       if (!pch)
+         return (1);
+       s++;
+       break;
+    }
+  }
+}
+
+/* --- @str_match@ --- *
+ *
+ * Arguments:  @const char *p@ = pointer to pattern string
+ *             @const char *s@ = string to compare with
+ *
+ * Returns:    Nonzero if the pattern matches the string.
+ *
+ * Use:                Does simple wildcard matching.  Equivalent to @str_matchx@
+ *             with zero flags word.
+ */
+
+int str_match(const char *p, const char *s)
+  { return (str_matchx(p, s, 0)); }
+
+/* --- @str_sanitize@ --- *
+ *
+ * Arguments:  @char *d@ = destination buffer
+ *             @const char *p@ = pointer to source string
+ *             @size_t sz@ = size of destination buffer
+ *
+ * Returns:    ---
+ *
+ * Use:                Writes a string into a buffer, being careful not to overflow
+ *             the buffer, to null terminate the result, and to prevent
+ *             nasty nonprintable characters ending up in the buffer.
+ */
+
+void str_sanitize(char *d, const char *p, size_t sz)
+{
+  if (!sz)
+    return;
+  sz--;
+  while (*p && sz) {
+    int ch = *p++;
+    if (!ISGRAPH(ch))
+      ch = '_';
+    *d++ = ch;
+    sz--;
+  }
+  *d++ = 0;
+}
+
+/*----- That's all, folks -------------------------------------------------*/
diff --git a/utils/str.h b/utils/str.h
new file mode 100644 (file)
index 0000000..facdba6
--- /dev/null
@@ -0,0 +1,163 @@
+/* -*-c-*-
+ *
+ * Functions for hacking with strings
+ *
+ * (c) 1999 Straylight/Edgeware
+ */
+
+/*----- Licensing notice --------------------------------------------------*
+ *
+ * This file is part of the mLib utilities library.
+ *
+ * mLib is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * mLib is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with mLib; if not, write to the Free
+ * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+ * MA 02111-1307, USA.
+ */
+
+#ifndef MLIB_STR_H
+#define MLIB_STR_H
+
+#ifdef __cplusplus
+  extern "C" {
+#endif
+
+/*----- Header files ------------------------------------------------------*/
+
+#include <stddef.h>
+
+/*----- Functions provided ------------------------------------------------*/
+
+/* --- @str_qword@ --- *
+ *
+ * Arguments:  @char **pp@ = address of pointer into string
+ *             @unsigned f@ = various flags
+ *
+ * Returns:    Pointer to the next space-separated possibly-quoted word from
+ *             the string, or null.
+ *
+ * Use:                Fetches the next word from a string.  If the flag
+ *             @STRF_QUOTE@ is set, the `\' character acts as an escape, and
+ *             single and double quotes protect whitespace.
+ */
+
+#define STRF_QUOTE 1u
+
+extern char *str_qword(char **/*pp*/, unsigned /*f*/);
+
+/* --- @str_qsplit@ --- *
+ *
+ * Arguments:  @char *p@ = pointer to string
+ *             @char *v[]@ = pointer to array to fill in
+ *             @size_t c@ = count of strings to fill in
+ *             @char **rest@ = where to store the remainder of the string
+ *             @unsigned f@ = flags for @str_qword@
+ *
+ * Returns:    Number of strings filled in.
+ *
+ * Use:                Fills an array with pointers to the individual words of a
+ *             string.  The string is modified in place to contain zero
+ *             bytes at the word boundaries, and the words have leading
+ *             and trailing space stripped off.  No more than @c@ words
+ *             are read; the actual number is returned as the value of the
+ *             function.  Unused slots in the array are populated with
+ *             null bytes.  If there's any string left, the address of the
+ *             remainder is stored in @rest@ (if it's non-null); otherwise
+ *             @rest@ is set to a null pointer.
+ */
+
+extern size_t str_qsplit(char */*p*/, char */*v*/[], size_t /*c*/,
+                        char **/*rest*/, unsigned /*f*/);
+
+/* --- @str_getword@ --- *
+ *
+ * Arguments:  @char **pp@ = address of pointer into string
+ *
+ * Returns:    Pointer to the next space-separated word from the string,
+ *             or null.
+ *
+ * Use:                Parses off space-separated words from a string.  This is a
+ *             compatibility veneer over @str_qword@.
+ */
+
+extern char *str_getword(char **/*pp*/);
+
+/* --- @str_split@ --- *
+ *
+ * Arguments:  @char *p@ = pointer to string
+ *             @char *v[]@ = pointer to array to fill in
+ *             @size_t c@ = count of strings to fill in
+ *             @char **rest@ = where to store the remainder of the string
+ *
+ * Returns:    Number of strings filled in.
+ *
+ * Use:                Fills an array with pointers to the individual words of a
+ *             string.  This is a compatibility veneer over @str_qsplit@.
+ */
+
+extern size_t str_split(char */*p*/, char */*v*/[],
+                       size_t /*c*/, char **/*rest*/);
+
+/* --- @str_matchx@ --- *
+ *
+ * Arguments:  @const char *p@ = pointer to pattern string
+ *             @const char *s@ = string to compare with
+ *             @unsigned f@ = various flags
+ *
+ * Returns:    Nonzero if the pattern matches the string.
+ *
+ * Use:                Does simple wildcard matching.  This is quite nasty and more
+ *             than a little slow.  Supports metacharacters `*', `?' and
+ *             '['.
+ */
+
+#define STRF_PREFIX 1u                 /* Accept if @s@ is exhausted during
+                                        *   the attempted match */
+
+extern int str_matchx(const char */*p*/, const char */*s*/, unsigned /*f*/);
+
+/* --- @str_match@ --- *
+ *
+ * Arguments:  @const char *p@ = pointer to pattern string
+ *             @const char *s@ = string to compare with
+ *
+ * Returns:    Nonzero if the pattern matches the string.
+ *
+ * Use:                Does simple wildcard matching.  Equivalent to @str_matchx@
+ *             with zero flags word.
+ */
+
+extern int str_match(const char */*p*/, const char */*s*/);
+
+/* --- @str_sanitize@ --- *
+ *
+ * Arguments:  @char *d@ = destination buffer
+ *             @const char *p@ = pointer to source string
+ *             @size_t sz@ = size of destination buffer
+ *
+ * Returns:    ---
+ *
+ * Use:                Writes a string into a buffer, being careful not to overflow
+ *             the buffer, to null terminate the result, and to prevent
+ *             nasty nonprintable characters ending up in the buffer.
+ */
+
+extern void str_sanitize(char */*d*/, const char */*p*/, size_t /*sz*/);
+
+/*----- That's all, folks -------------------------------------------------*/
+
+#ifdef __cplusplus
+  }
+#endif
+
+#endif
diff --git a/utils/t/bits-test.c b/utils/t/bits-test.c
new file mode 100644 (file)
index 0000000..0d0f66c
--- /dev/null
@@ -0,0 +1,102 @@
+/* -*-c-*-
+ *
+ * Test rig for bits header
+ *
+ * (c) 2000 Straylight/Edgeware
+ */
+
+/*----- Licensing notice --------------------------------------------------*
+ *
+ * This file is part of the mLib utilities library.
+ *
+ * mLib is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * mLib is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with mLib; if not, write to the Free
+ * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+ * MA 02111-1307, USA.
+ */
+
+/*----- Header files ------------------------------------------------------*/
+
+#include "bits.h"
+#include "testrig.h"
+
+/*----- Main code ---------------------------------------------------------*/
+
+#define TSHIFT(OP)                                                     \
+                                                                       \
+static int t##OP(dstr *v)                                              \
+{                                                                      \
+  kludge64 x, xx, y;                                                   \
+  unsigned s = *(unsigned *)v[1].buf;                                  \
+  int ok = 1;                                                          \
+                                                                       \
+  LOAD64_(x, v[0].buf);                                                        \
+  LOAD64_(xx, v[2].buf);                                               \
+  y = x;                                                               \
+  OP##64_(y, y, s);                                                    \
+  if (CMP64(y, !=, xx)) {                                              \
+    ok = 0;                                                            \
+    fprintf(stderr,                                                    \
+           "\nbad: %08x:%08x " #OP " %u != %08x:%08x [%08x:%08x]\n",   \
+           HI64(x), LO64(x), s, HI64(y), LO64(y), HI64(xx), LO64(xx)); \
+  }                                                                    \
+  return (ok);                                                         \
+}
+
+#define TARITH(OP)                                                     \
+                                                                       \
+static int t##OP(dstr *v)                                              \
+{                                                                      \
+  kludge64 x, y, xx, yy;                                               \
+  int ok = 1;                                                          \
+                                                                       \
+  LOAD64_(x, v[0].buf);                                                        \
+  LOAD64_(y, v[1].buf);                                                        \
+  LOAD64_(xx, v[2].buf);                                               \
+  yy = x;                                                              \
+  OP##64(yy, yy, y);                                                   \
+  if (CMP64(yy, !=, xx)) {                                             \
+    ok = 0;                                                            \
+    fprintf(stderr,                                                    \
+           "\nbad: %08x:%08x " #OP " %08x:%08x != %08x:%08x "          \
+           "[%08x:%08x]\n",                                            \
+           HI64(x), LO64(x), HI64(y), LO64(y),                         \
+           HI64(yy), LO64(yy), HI64(xx), LO64(xx));                    \
+  }                                                                    \
+  return (ok);                                                         \
+}
+
+TSHIFT(LSL)
+TSHIFT(LSR)
+TSHIFT(ROL)
+TSHIFT(ROR)
+TARITH(ADD)
+TARITH(SUB)
+
+static test_chunk tests[] = {
+  { "lsl64", tLSL, { &type_hex, &type_int, &type_hex, 0 } },
+  { "lsr64", tLSR, { &type_hex, &type_int, &type_hex, 0 } },
+  { "rol64", tROL, { &type_hex, &type_int, &type_hex, 0 } },
+  { "ror64", tROR, { &type_hex, &type_int, &type_hex, 0 } },
+  { "add64", tADD, { &type_hex, &type_hex, &type_hex, 0 } },
+  { "sub64", tSUB, { &type_hex, &type_hex, &type_hex, 0 } },
+  { 0, 0, { 0 } }
+};
+
+int main(int argc, char *argv[])
+{
+  test_run(argc, argv, tests, SRCDIR "/t/bits.tests");
+  return (0);
+}
+
+/*----- That's all, folks -------------------------------------------------*/
diff --git a/utils/t/bits-testgen.py b/utils/t/bits-testgen.py
new file mode 100644 (file)
index 0000000..c43f496
--- /dev/null
@@ -0,0 +1,57 @@
+#! /usr/bin/python
+### -*-python-*-
+###
+### Generate input script and expected output for bit manipulation,
+### particularly 64-bit arithmetic.
+
+import sys as SYS
+import random as R
+
+if SYS.version_info >= (3,): xrange = range
+
+NVEC = 64
+WD = 64
+LIMIT = 1 << WD
+MASK = LIMIT - 1
+
+ARGS = SYS.argv[1:]; ARGS.reverse()
+def arg(default = None):
+  if len(ARGS): return ARGS.pop()
+  else: return default
+
+R.seed(None)
+seed = arg()
+if seed is None: SEED = R.randrange(0, 1 << 32)
+else: SEED = int(seed, 0)
+R.seed(SEED)
+
+print('### Test vectors for 64-bit arithmetic macros')
+print('###   [generated; seed = 0x%08x]' % SEED)
+
+def rol(x, n): return ((x << n) | (x >> (WD - n))) & MASK
+def ror(x, n): return ((x >> n) | (x << (WD - n))) & MASK
+def put(x): return '%0*x' % (WD//4, x)
+
+for name, func in [('lsl', lambda x, n: x << n),
+                   ('lsr', lambda x, n: x >> n),
+                   ('rol', rol),
+                   ('ror', ror)]:
+  print('\n%s64 {' % name)
+  for i in xrange(NVEC):
+    x = R.randrange(LIMIT)
+    sh = R.randrange(0, 70) & 63
+    print('  %s %2d %s;' % (put(x), sh, put(func(x, sh) & MASK)))
+  for i in xrange(4):
+    x = R.randrange(LIMIT)
+    sh = 32
+    print('  %s %2d %s;' % (put(x), sh, put(func(x, sh) & MASK)))
+  print('}')
+
+for name, func in [('add', lambda x, y: x + y),
+                   ('sub', lambda x, y: x - y)]:
+  print('\n%s64 {' % name)
+  for i in xrange(NVEC):
+    x = R.randrange(LIMIT)
+    y = R.randrange(LIMIT)
+    print('  %s %s %s;' % (put(x), put(y), put(func(x, y) & MASK)))
+  print('}')
diff --git a/utils/t/exc-test.c b/utils/t/exc-test.c
new file mode 100644 (file)
index 0000000..89701bf
--- /dev/null
@@ -0,0 +1,44 @@
+#include <stdio.h>
+#include "exc.h"
+
+void func(void)
+{
+  printf("cabbage\n");
+  TRY {
+    printf("dibble\n");
+    THROW(EXC_FAIL, "excession");
+    printf("can't see me\n");
+  } CATCH switch (exc_type) {
+    case 1:
+      printf("exc type 1 (not possible)\n");
+      break;
+    case EXC_FAIL:
+      printf("exc type 2 (%s)\n", exc_s);
+      break;
+    default:
+      RETHROW;
+  } END_TRY;
+
+  printf("fennel\n");
+  THROW(EXC_ERRNO, 53);
+}
+
+int main(void)
+{
+  printf("apple\n");
+  TRY {
+    printf("banana\n");
+    func();
+    printf("can't see me\n");
+  } CATCH switch (exc_type) {
+    default:
+      printf("%lu exception (val = %i)\n", exc_type, exc_i);
+      break;
+  } END_TRY;
+  printf("hello! __exc_list = ");
+  if (__exc_list) printf("%p", (void *)__exc_list);
+  else printf("(nil)");
+  putchar('\n');
+
+  return (0);
+}
diff --git a/utils/t/versioncmp-test.c b/utils/t/versioncmp-test.c
new file mode 100644 (file)
index 0000000..0927f46
--- /dev/null
@@ -0,0 +1,51 @@
+/* -*-c-*-
+ *
+ * Test rig for versioncmp
+ *
+ * (c) 2008 Straylight/Edgeware
+ */
+
+/*----- Licensing notice --------------------------------------------------*
+ *
+ * This file is part of the mLib utilities library.
+ *
+ * mLib is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * mLib is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with mLib; if not, write to the Free
+ * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+ * MA 02111-1307, USA.
+ */
+
+#include "testrig.h"
+#include "versioncmp.h"
+
+static int t_vcmp(dstr *v)
+{
+  int ok = 1;
+  int rc = versioncmp(v[0].buf, v[1].buf);
+  if (rc != *(int *)v[2].buf) {
+    fprintf(stderr, "\nversioncmp(%s, %s) returns %d != %d\n",
+           v[0].buf, v[1].buf, rc, *(int *)v[2].buf);
+    ok = 0;
+  }
+  return (ok);
+}
+
+static const test_chunk tests[] = {
+  { "versioncmp", t_vcmp, { &type_string, &type_string, &type_int } },
+  { 0 }
+};
+
+int main(int argc, char *argv[])
+  { test_run(argc, argv, tests, SRCDIR "/versioncmp.in"); return (1); }
+
+/*----- That's all, folks -------------------------------------------------*/
diff --git a/utils/t/versioncmp.tests b/utils/t/versioncmp.tests
new file mode 100644 (file)
index 0000000..421974a
--- /dev/null
@@ -0,0 +1,14 @@
+## test for versioncmp
+
+versioncmp {
+  1.2 1.2 0;
+  1.1 1.2 -1;
+  1.1 1.0 +1;
+  1.0 1.a -1;
+  1:2.0 2:0.4 -1;
+  1.2 1.2~pre0 +1;
+  1~~ 1~~a -1;
+  1~~a 1~ -1;
+  1~ 1 -1;
+  1 1a -1;
+}
diff --git a/utils/tests.at b/utils/tests.at
new file mode 100644 (file)
index 0000000..3b216fc
--- /dev/null
@@ -0,0 +1,62 @@
+### -*-autotest-*-
+###
+### Test script for utilities
+###
+### (c) 2009 Straylight/Edgeware
+###
+
+###----- Licensing notice ---------------------------------------------------
+###
+### This file is part of the mLib utilities library.
+###
+### mLib is free software; you can redistribute it and/or modify
+### it under the terms of the GNU Library General Public License as
+### published by the Free Software Foundation; either version 2 of the
+### License, or (at your option) any later version.
+###
+### mLib is distributed in the hope that it will be useful,
+### but WITHOUT ANY WARRANTY; without even the implied warranty of
+### MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+### GNU Library General Public License for more details.
+###
+### You should have received a copy of the GNU Library General Public
+### License along with mLib; if not, write to the Free
+### Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+### MA 02111-1307, USA.
+
+###--------------------------------------------------------------------------
+### Tests.
+
+## bits
+AT_SETUP([utilities: bits])
+AT_KEYWORDS([utils bits])
+for seed in 0xaca98e08 0x0b6e95fb ""; do
+  $PYTHON SRCDIR/t/bits-testgen.py $seed >bits.tests
+  AT_CHECK([BUILDDIR/t/bits.t -f bits.tests], [0], [ignore], [ignore])
+done
+AT_CLEANUP
+
+## exc
+AT_SETUP([utilities: exc])
+AT_KEYWORDS([utils exc])
+AT_DATA([expout],
+[apple
+banana
+cabbage
+dibble
+exc type 2 (excession)
+fennel
+65 exception (val = 53)
+hello! __exc_list = (nil)
+])
+AT_CHECK([BUILDDIR/t/exc.t], [0], [expout])
+AT_CLEANUP
+
+## versioncmp
+AT_SETUP([utilities: versioncmp])
+AT_KEYWORDS([utils versioncmp])
+AT_CHECK([BUILDDIR/t/versioncmp.t -f SRCDIR/t/versioncmp.tests],
+        [0], [ignore], [ignore])
+AT_CLEANUP
+
+###----- That's all, folks --------------------------------------------------
diff --git a/utils/versioncmp.3 b/utils/versioncmp.3
new file mode 100644 (file)
index 0000000..15d1593
--- /dev/null
@@ -0,0 +1,65 @@
+.\" -*-nroff-*-
+.TH versioncmp 3 "6 January 2007" "Straylight/Edgeware" "mLib utilities library"
+.SH NAME
+versioncmp \- compare Debian-format version numbers
+.\" @versioncmp
+.\" @VERSIONCMP
+.SH SYNOPSIS
+.nf
+.B "#include <mLib/versioncmp.h>"
+
+.BI "int versioncmp(const char *" va ", const char *" vb ");"
+.BI "int VERSIONCMP(const char *" va ", " op ", const char *" vb ");"
+.fi
+.SH DESCRIPTION
+The
+.B versioncmp
+function compares version strings.
+.PP
+The format of version numbers considered is
+.IP
+.RI [ epoch
+.BR : ]
+.I main
+.RB [ \-
+.IR sub ]
+.PP
+The
+.I main
+part may contain colons or hyphens if there is an
+.I epoch
+or
+.IR sub ,
+respectively.  Version strings are compared componentwise: first epochs,
+then main parts, and finally subparts.
+.PP
+The component comparison is done as follows.  First, the initial
+subsequence of nondigit characters is extracted from each string, and
+these are compared lexicographically, using ASCII ordering, except that
+letters precede non-letters.  If both are the same, an initial sequence
+of digits is extracted from the remaining parts of the version strings,
+and these are compared numerically (an empty sequence being considered
+to have the value zero).  This process is repeated until we have a
+winner or until both strings are exhausted.
+.PP
+The return value is 0 if the two strings are equal, \-1 if
+.I va
+is older than
+.IR vb ,
+or +1 if
+.I va
+is newer than
+.IR vb .
+.PP
+The
+.B VERSIONCMP
+macro provides a more convenient syntax for the
+.B versioncmp
+function, by allowing a relational operator to be written between the
+operands.
+.SH SEE ALSO
+.BR mLib (3).
+.PP
+.IR "Debian Policy Manual" .
+.SH AUTHOR
+Mark Wooding, <mdw@distorted.org.uk>
diff --git a/utils/versioncmp.c b/utils/versioncmp.c
new file mode 100644 (file)
index 0000000..e8bf9ae
--- /dev/null
@@ -0,0 +1,189 @@
+/* -*-c-*-
+ *
+ * Compare version numbers using the Debian algorithm
+ *
+ * (c) 2007 Straylight/Edgeware
+ */
+
+/*----- Licensing notice --------------------------------------------------*
+ *
+ * This file is part of the mLib utilities library.
+ *
+ * mLib is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * mLib is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with mLib; if not, write to the Free
+ * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+ * MA 02111-1307, USA.
+ */
+
+/*----- Header files ------------------------------------------------------*/
+
+#include <ctype.h>
+#include <string.h>
+
+#include "macros.h"
+#include "versioncmp.h"
+
+/*----- Main code ---------------------------------------------------------*/
+
+/* --- @versioncmp@ --- *
+ *
+ * Arguments:  @const char *va, *vb@ = two version strings
+ *
+ * Returns:    Less than, equal to, or greater than zero, according to
+ *             whether @va@ is less than, equal to, or greater than @vb@.
+ *
+ * Use:                Compares version number strings.
+ *
+ *             The algorithm is an extension of the Debian version
+ *             comparison algorithm.  A version number consists of three
+ *             components:
+ *
+ *               [EPOCH :] MAIN [- SUB]
+ *
+ *             The MAIN part may contain colons or hyphens if there is an
+ *             EPOCH or SUB, respectively.  Version strings are compared
+ *             componentwise: first epochs, then main parts, and finally
+ *             subparts.
+ *
+ *             The component comparison is done as follows.  First, the
+ *             initial subsequence of nondigit characters is extracted from
+ *             each string, and these are compared lexicographically, using
+ *             ASCII ordering, except that letters precede non-letters.  If
+ *             both are the same, an initial sequence of digits is extracted
+ *             from the remaining parts of the version strings, and these
+ *             are compared numerically (an empty sequence being considered
+ *             to have the value zero).  This process is repeated until we
+ *             have a winner or until both strings are exhausted.
+ */
+
+struct vinfo {
+  const char *e, *el;
+  const char *m, *ml;
+  const char *s, *sl;
+};
+
+static int vint(const char **vv, const char *vl)
+{
+  int n = 0;
+  const char *v = *vv;
+  int ch;
+
+  while (v < vl) {
+    ch = *v;
+    if (!ISDIGIT(ch))
+      break;
+    v++;
+    n = n * 10 + (ch - '0');
+  }
+  *vv = v;
+  return (n);
+}
+
+static const char *vchr(const char **vv, const char *vl)
+{
+  const char *v = *vv;
+  const char *b = v;
+  int ch;
+
+  while (v < vl) {
+    ch = *v;
+    if (ISDIGIT(ch))
+      break;
+    v++;
+  }
+  *vv = v;
+  return (b);
+}
+
+#define CMP(x, y) ((x) < (y) ? -1 : +1)
+
+static int vcmp(const char *va, const char *val,
+               const char *vb, const char *vbl)
+{
+  const char *pa, *pb;
+  int ia, ib;
+
+  for (;;) {
+
+    /* --- See if we're done --- */
+
+    if (va == val && vb == vbl)
+      return (0);
+
+    /* --- Compare nondigit portions --- */
+
+    pa = vchr(&va, val); pb = vchr(&vb, vbl);
+    for (;;) {
+      if (pa == va) ia = 1;
+      else if (ISALPHA(*pa)) ia = 2;
+      else if (*pa == '~') ia = 0;
+      else ia = 3;
+
+      if (pb == vb) ib = 1;
+      else if (ISALPHA(*pb)) ib = 2;
+      else if (*pb == '~') ib = 0;
+      else ib = 3;
+
+      if (ia != ib) return (CMP(ia, ib));
+      else if (pa == va && pb == vb) break;
+      else if (*pa != *pb) return (CMP(*pa, *pb));
+      pa++; pb++;
+    }
+
+    /* --- Compare digit portions --- */
+
+    ia = vint(&va, val); ib = vint(&vb, vbl);
+    if (ia != ib) return (CMP(ia, ib));
+  }
+}
+
+static void vsplit(const char *v, struct vinfo *vi)
+{
+  const char *p;
+  size_t n;
+
+  if ((p = strchr(v, ':')) == 0)
+    vi->e = vi->el = 0;
+  else {
+    vi->e = v;
+    vi->el = p;
+    v = p + 1;
+  }
+
+  n = strlen(v);
+  if ((p = strrchr(v, '-')) == 0)
+    vi->s = vi->sl = 0;
+  else {
+    vi->s = p + 1;
+    vi->sl = v + n;
+    n = p - v;
+  }
+
+  vi->m = v;
+  vi->ml = v + n;
+}
+
+int versioncmp(const char *va, const char *vb)
+{
+  struct vinfo via, vib;
+  int rc;
+
+  vsplit(va, &via); vsplit(vb, &vib);
+  if ((rc = vcmp(via.e, via.el, vib.e, vib.el)) != 0 ||
+      (rc = vcmp(via.m, via.ml, vib.m, vib.ml)) != 0 ||
+      (rc = vcmp(via.s, via.sl, vib.s, vib.sl)) != 0)
+    return (rc);
+  return (0);
+}
+
+/*----- That's all, folks -------------------------------------------------*/
diff --git a/utils/versioncmp.h b/utils/versioncmp.h
new file mode 100644 (file)
index 0000000..1990cc3
--- /dev/null
@@ -0,0 +1,77 @@
+/* -*-c-*-
+ *
+ * Compare version numbers using the Debian algorithm
+ *
+ * (c) 2007 Straylight/Edgeware
+ */
+
+/*----- Licensing notice --------------------------------------------------*
+ *
+ * This file is part of the mLib utilities library.
+ *
+ * mLib is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * mLib is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with mLib; if not, write to the Free
+ * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+ * MA 02111-1307, USA.
+ */
+
+#ifndef MLIB_VERSIONCMP_H
+#define MLIB_VERSIONCMP_H
+
+#ifdef __cplusplus
+  extern "C" {
+#endif
+
+/*----- Functions provided ------------------------------------------------*/
+
+/* --- @versioncmp@ --- *
+ *
+ * Arguments:  @const char *va, *vb@ = two version strings
+ *
+ * Returns:    Less than, equal to, or greater than zero, according to
+ *             whether @va@ is less than, equal to, or greater than @vb@.
+ *
+ * Use:                Compares version number strings.
+ *
+ *             The algorithm is an extension of the Debian version
+ *             comparison algorithm.  A version number consists of three
+ *             components:
+ *
+ *               [EPOCH :] MAIN [- SUB]
+ *
+ *             The MAIN part may contain colons or hyphens if there is an
+ *             EPOCH or SUB, respectively.  Version strings are compared
+ *             componentwise: first epochs, then main parts, and finally
+ *             subparts.
+ *
+ *             The component comparison is done as follows.  First, the
+ *             initial subsequence of nondigit characters is extracted from
+ *             each string, and these are compared lexicographically, using
+ *             ASCII ordering, except that letters precede non-letters.  If
+ *             both are the same, an initial sequence of digits is extracted
+ *             from the remaining parts of the version strings, and these
+ *             are compared numerically (an empty sequence being considered
+ *             to have the value zero).  This process is repeated until we
+ *             have a winner or until both strings are exhausted.
+ */
+
+extern int versioncmp(const char */*va*/, const char */*vb*/);
+#define VERSIONCMP(x, op, y) (versioncmp((x), (y)) op 0)
+
+/*----- That's all, folks -------------------------------------------------*/
+
+#ifdef __cplusplus
+  }
+#endif
+
+#endif
diff --git a/vars.am b/vars.am
new file mode 100644 (file)
index 0000000..9ce78f9
--- /dev/null
+++ b/vars.am
@@ -0,0 +1,148 @@
+### -*-Makefile-*-
+###
+### Common build-system definition
+###
+### (c) 1998 Straylight/Edgeware
+###
+
+###----- Licensing notice ---------------------------------------------------
+###
+### This file is part of the mLib utilities library.
+###
+### mLib is free software; you can redistribute it and/or modify
+### it under the terms of the GNU Library General Public License as
+### published by the Free Software Foundation; either version 2 of the
+### License, or (at your option) any later version.
+###
+### mLib is distributed in the hope that it will be useful,
+### but WITHOUT ANY WARRANTY; without even the implied warranty of
+### MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+### GNU Library General Public License for more details.
+###
+### You should have received a copy of the GNU Library General Public
+### License along with mLib; if not, write to the Free
+### Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+### MA 02111-1307, USA.
+
+###--------------------------------------------------------------------------
+### Miscellaneous useful definitions.
+
+## Installation directories.
+pkglibexecdir           = $(libexecdir)/$(PACKAGE)
+
+###--------------------------------------------------------------------------
+### Initial values for common variables.
+
+EXTRA_DIST              =
+CLEANFILES              =
+DISTCLEANFILES          =
+MAINTAINERCLEANFILES    =
+BUILT_SOURCES           =
+
+bin_PROGRAMS            =
+check_PROGRAMS          =
+pkginclude_HEADERS      =
+
+PROGMANS                =
+LIBMANS                         =
+EXTRA_DIST             += $(PROGMANS) $(LIBMANS)
+
+###--------------------------------------------------------------------------
+### Machinery for precomputations.
+
+## Location of precomputed tables.
+precomp                         = $(top_srcdir)/precomp
+
+## Precomputed source code files.
+PRECOMPS                =
+EXTRA_DIST             += $(PRECOMPS)
+BUILT_SOURCES          += $(PRECOMPS)
+MAINTAINERCLEANFILES   += $(PRECOMPS)
+
+###--------------------------------------------------------------------------
+### Include path.
+
+MLIB_INCLUDES           = \
+       -I$(top_srcdir)/buf \
+       -I$(top_srcdir)/codec \
+       -I$(top_srcdir)/hash \
+       -I$(top_srcdir)/mem \
+       -I$(top_srcdir)/net \
+       -I$(top_srcdir)/struct \
+       -I$(top_srcdir)/sys \
+       -I$(top_srcdir)/test \
+       -I$(top_srcdir)/trace \
+       -I$(top_srcdir)/ui \
+       -I$(top_srcdir)/utils
+
+AM_CPPFLAGS             = $(MLIB_INCLUDES)
+
+###--------------------------------------------------------------------------
+### Standard configuration substitutions.
+
+## Substitute tags in files.
+confsubst               = $(top_srcdir)/config/confsubst
+
+SUBSTITUTIONS = \
+       prefix=$(prefix) exec_prefix=$(exec_prefix) \
+       libdir=$(libdir) includedir=$(includedir) \
+       bindir=$(bindir) sbindir=$(sbindir) \
+       PACKAGE=$(PACKAGE) VERSION=$(VERSION) \
+       MLIB_LIBS="$(MLIB_LIBS)"
+
+V_SUBST                         = $(V_SUBST_@AM_V@)
+V_SUBST_                = $(V_SUBST_@AM_DEFAULT_V@)
+V_SUBST_0               = @echo "  SUBST    $@";
+SUBST                   = $(V_SUBST)$(confsubst)
+
+###--------------------------------------------------------------------------
+### Building utilities.
+
+## Which libraries we need.
+UTIL_LIBS = \
+       $(top_builddir)/ui/libui.la \
+       $(top_builddir)/utils/libutils.la
+
+###--------------------------------------------------------------------------
+### Testing.
+
+TEST_CPPFLAGS           = -DTEST_RIG -DSRCDIR="\"$(srcdir)\"" $(AM_CPPFLAGS)
+LDADD                   = $(top_builddir)/libmLib.la
+
+check: tests
+.PHONY: check tests
+
+tests::;
+.PHONY: tests
+
+###--------------------------------------------------------------------------
+### Manual.
+
+EXTRA_DIST             += $(LIBMANS) $(PROGMANS)
+
+install-data-local: install-man
+install-man: $(LIBMANS) $(PROGMANS)
+       @$(NORMAL_INSTALL)
+       $(mkdir_p) $(DESTDIR)$(mandir)
+       $(top_srcdir)/config/maninst \
+               -d $(DESTDIR)$(mandir) -s $(srcdir) \
+               -i "$(INSTALL)" \
+               install $(PROGMANS)
+       $(top_srcdir)/config/maninst \
+               -d $(DESTDIR)$(mandir) -s $(srcdir) \
+               -i "$(INSTALL)" -e "$(manext)" \
+               install $(LIBMANS)
+.PHONY: install-man
+
+uninstall-local: uninstall-man
+uninstall-man:
+       @$(NORMAL_UNINSTALL)
+       $(top_srcdir)/config/maninst \
+               -d $(DESTDIR)$(mandir) -s $(srcdir) \
+               uninstall $(PROGMANS)
+       $(top_srcdir)/config/maninst \
+               -d $(DESTDIR)$(mandir) -s $(srcdir) -e "$(manext)" \
+               uninstall $(LIBMANS)
+.PHONY: uninstall-man
+
+###----- That's all, folks --------------------------------------------------