--- /dev/null
+;;; -*-emacs-lisp-*-
+
+(setq skel-alist
+ (append
+ '((author . "Mark Wooding")
+ (full-title . "the Common Files Distribution (`common')")
+ (Program . "`Common'")
+ (program . "`common'"))
+ skel-alist))
--- /dev/null
+## 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
--- /dev/null
+config/maninst
+config/auto-version
+config/confsubst
+ui/mdwopt.c
+ui/mdwopt.h
+t/autotest.am
+t/testsuite.at
+COPYING
+COPYING.LIB
--- /dev/null
+Mark Wooding <mdw@distorted.org.uk> <mdw>
(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))
--- /dev/null
+### -*-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 --------------------------------------------------
--- /dev/null
+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:
--- /dev/null
+### -*-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 --------------------------------------------------
--- /dev/null
+.\" -*-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>
--- /dev/null
+/* -*-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 -------------------------------------------------*/
--- /dev/null
+/* -*-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
--- /dev/null
+.\" -*-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>
--- /dev/null
+/* -*-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 -------------------------------------------------*/
--- /dev/null
+/* -*-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
--- /dev/null
+### -*-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 --------------------------------------------------
--- /dev/null
+/* -*-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
--- /dev/null
+.\" -*-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>
--- /dev/null
+/* -*-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
--- /dev/null
+/* -*-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 -------------------------------------------------*/
--- /dev/null
+.\" 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>
+
--- /dev/null
+/* -*-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 -------------------------------------------------*/
--- /dev/null
+.\" -*-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>
--- /dev/null
+/* -*-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 -------------------------------------------------*/
--- /dev/null
+/* -*-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
--- /dev/null
+/* -*-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
--- /dev/null
+/* -*-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 -------------------------------------------------*/
--- /dev/null
+### -*-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 --------------------------------------------------
--- /dev/null
+.\" -*-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>.
--- /dev/null
+/* -*-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 -------------------------------------------------*/
--- /dev/null
+/* -*-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
--- /dev/null
+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 -------------------------------------------------
--- /dev/null
+## 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
--- /dev/null
+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
--- /dev/null
+### -*-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 --------------------------------------------------
--- /dev/null
+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.
--- /dev/null
+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'.
--- /dev/null
+debian/tmp/usr/bin
+debian/tmp/usr/share/man/man1
--- /dev/null
+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
--- /dev/null
+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.
--- /dev/null
+debian/tmp-adns/usr/lib/@ARCH@/libmLib.so.* /usr/lib/@ARCH@
--- /dev/null
+#include "common.symbols"
--- /dev/null
+debian/tmp/usr/lib/*/libmLib.so.*
+debian/tmp/usr/lib/*/mLib/bres
--- /dev/null
+#include "common.symbols"
--- /dev/null
+#! /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 --------------------------------------------------
--- /dev/null
+### -*-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 --------------------------------------------------
--- /dev/null
+.\" 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>
+
--- /dev/null
+/* -*-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 -------------------------------------------------*/
--- /dev/null
+.\" -*-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>
--- /dev/null
+/* -*-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 -------------------------------------------------*/
--- /dev/null
+/* -*-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
--- /dev/null
+/* -*-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 -------------------------------------------------*/
--- /dev/null
+### -*-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;
+}
--- /dev/null
+/* -*-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 -------------------------------------------------*/
--- /dev/null
+#! /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('}')
--- /dev/null
+### -*-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 --------------------------------------------------
--- /dev/null
+.\" 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>
+
--- /dev/null
+/* -*-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 -------------------------------------------------*/
--- /dev/null
+.\" -*-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).
--- /dev/null
+/* -*-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 -------------------------------------------------*/
--- /dev/null
+/* -*-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
--- /dev/null
+.\" -*-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>
--- /dev/null
+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}
--- /dev/null
+### -*-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 --------------------------------------------------
--- /dev/null
+.\" -*-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>
+
--- /dev/null
+/* -*-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 -------------------------------------------------*/
--- /dev/null
+/* -*-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
--- /dev/null
+.\" -*-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>
--- /dev/null
+/* -*-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 -------------------------------------------------*/
--- /dev/null
+/* -*-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
--- /dev/null
+/* -*-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 -------------------------------------------------*/
--- /dev/null
+/* -*-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 -------------------------------------------------*/
--- /dev/null
+.\" -*-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>
--- /dev/null
+/* -*-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 -------------------------------------------------*/
--- /dev/null
+/* -*-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
--- /dev/null
+.\" -*-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>
--- /dev/null
+/* -*-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 -------------------------------------------------*/
--- /dev/null
+/* -*-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
--- /dev/null
+### -*-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 --------------------------------------------------
--- /dev/null
+/* -*-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 -------------------------------------------------*/
--- /dev/null
+.\" -*-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>
--- /dev/null
+/* -*-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 -------------------------------------------------*/
--- /dev/null
+/* -*-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
--- /dev/null
+.\" -*-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>
--- /dev/null
+/* -*-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 -------------------------------------------------*/
--- /dev/null
+/* -*-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
--- /dev/null
+.\" -*-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>
--- /dev/null
+/* -*-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 -------------------------------------------------*/
--- /dev/null
+/* -*-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
--- /dev/null
+.\" -*-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>
--- /dev/null
+/* -*-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 -------------------------------------------------*/
--- /dev/null
+/* -*-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
--- /dev/null
+.\" -*-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>
--- /dev/null
+/* -*-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 -------------------------------------------------*/
--- /dev/null
+/* -*-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
--- /dev/null
+.\" -*-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>
--- /dev/null
+/* -*-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 -------------------------------------------------*/
--- /dev/null
+/* -*-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
--- /dev/null
+.\" -*-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>
--- /dev/null
+/* -*-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 -------------------------------------------------*/
--- /dev/null
+/* -*-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
--- /dev/null
+### -*-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 --------------------------------------------------
--- /dev/null
+.\" -*-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>
--- /dev/null
+/* -*-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 -------------------------------------------------*/
--- /dev/null
+/* -*-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
--- /dev/null
+.\" -*-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>
--- /dev/null
+/* -*-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 -------------------------------------------------*/
--- /dev/null
+/* -*-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
--- /dev/null
+/* -*-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 -------------------------------------------------*/
--- /dev/null
+.\" -*-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>
--- /dev/null
+/* -*-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 -------------------------------------------------*/
--- /dev/null
+/* -*-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
--- /dev/null
+.\" -*-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>
--- /dev/null
+/* -*-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 -------------------------------------------------*/
--- /dev/null
+/* -*-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
--- /dev/null
+.\" -*-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>
--- /dev/null
+/* -*-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 -------------------------------------------------*/
--- /dev/null
+/* -*-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
--- /dev/null
+/* -*-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 -------------------------------------------------*/
--- /dev/null
+.\" -*-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>
--- /dev/null
+/* -*-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 -------------------------------------------------*/
--- /dev/null
+/* -*-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
--- /dev/null
+.\" -*-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>
--- /dev/null
+/* -*-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 -------------------------------------------------*/
--- /dev/null
+/* -*-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
--- /dev/null
+.\" -*-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>
--- /dev/null
+/* -*-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 -------------------------------------------------*/
--- /dev/null
+/* -*-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
--- /dev/null
+#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);
+}
--- /dev/null
+#! /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 --------------------------------------------------
--- /dev/null
+/* -*-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);
+}
--- /dev/null
+#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);
+}
--- /dev/null
+#! /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 --------------------------------------------------
--- /dev/null
+#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);
+}
--- /dev/null
+### -*-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 --------------------------------------------------
--- /dev/null
+### -*-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 --------------------------------------------------
--- /dev/null
+.\" -*-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>
--- /dev/null
+/* -*-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 -------------------------------------------------*/
--- /dev/null
+/* -*-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
--- /dev/null
+.\" -*-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>
--- /dev/null
+/* -*-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 -------------------------------------------------*/
--- /dev/null
+/* -*-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
--- /dev/null
+.\" -*-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>
--- /dev/null
+/* -*-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 -------------------------------------------------*/
--- /dev/null
+/* -*-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
--- /dev/null
+.\" -*-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>
--- /dev/null
+/* -*-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 -------------------------------------------------*/
--- /dev/null
+/* -*-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
--- /dev/null
+.\" -*-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>
--- /dev/null
+/* -*-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 -------------------------------------------------*/
--- /dev/null
+/* -*-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
--- /dev/null
+.\" -*-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>
--- /dev/null
+/* -*-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 -------------------------------------------------*/
--- /dev/null
+/* -*-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
--- /dev/null
+.\" -*-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>
+
--- /dev/null
+/* -*-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 -------------------------------------------------*/
--- /dev/null
+/* -*-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
--- /dev/null
+/* -*-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 -------------------------------------------------*/
--- /dev/null
+#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);
+}
--- /dev/null
+### -*-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 --------------------------------------------------
--- /dev/null
+.\" -*-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>
--- /dev/null
+/* -*-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 -------------------------------------------------*/
--- /dev/null
+/* -*-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
--- /dev/null
+### -*-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 --------------------------------------------------
--- /dev/null
+### -*-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 --------------------------------------------------
--- /dev/null
+### -*-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 --------------------------------------------------
--- /dev/null
+.\" -*-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>
--- /dev/null
+/* -*-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 -------------------------------------------------*/
--- /dev/null
+/* -*-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
--- /dev/null
+### -*-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 --------------------------------------------------
--- /dev/null
+.\" -*-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>
--- /dev/null
+/* -*-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 -------------------------------------------------*/
--- /dev/null
+/* -*-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
--- /dev/null
+/* -*-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 -------------------------------------------------*/
--- /dev/null
+### -*-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 --------------------------------------------------
--- /dev/null
+.\" -*-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>
--- /dev/null
+/* -*-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 -------------------------------------------------*/
--- /dev/null
+.\" -*-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>
--- /dev/null
+/* -*-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 -------------------------------------------------*/
--- /dev/null
+/* -*-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
--- /dev/null
+.\" -*-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>
--- /dev/null
+/* -*-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 -------------------------------------------------*/
--- /dev/null
+/* -*-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
--- /dev/null
+### -*-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 --------------------------------------------------
--- /dev/null
+.\" -*-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>
--- /dev/null
+/* -*-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
--- /dev/null
+.\" -*-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>
+
--- /dev/null
+/* -*-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
--- /dev/null
+.\" -*-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>
--- /dev/null
+/* -*-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
--- /dev/null
+.\" -*-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>
--- /dev/null
+/* -*-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 -------------------------------------------------*/
--- /dev/null
+/* -*-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
--- /dev/null
+.\" -*-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>
--- /dev/null
+/* -*-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
--- /dev/null
+.\" -*-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>
--- /dev/null
+/* -*-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 -------------------------------------------------*/
--- /dev/null
+/* -*-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
--- /dev/null
+/* -*-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 -------------------------------------------------*/
--- /dev/null
+#! /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('}')
--- /dev/null
+#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);
+}
--- /dev/null
+/* -*-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 -------------------------------------------------*/
--- /dev/null
+## 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;
+}
--- /dev/null
+### -*-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 --------------------------------------------------
--- /dev/null
+.\" -*-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>
--- /dev/null
+/* -*-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 -------------------------------------------------*/
--- /dev/null
+/* -*-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
--- /dev/null
+### -*-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 --------------------------------------------------