summaryrefslogtreecommitdiff
path: root/news/nntpbtr
diff options
context:
space:
mode:
authorSatoshi Asami <asami@FreeBSD.org>1996-07-23 08:05:12 +0000
committerSatoshi Asami <asami@FreeBSD.org>1996-07-23 08:05:12 +0000
commit51a8267ea434c61482f49d200a24a1d8e6b1c623 (patch)
tree5b854643acf0e68afa5b7c0f39b20fa918a9c63e /news/nntpbtr
parentRemove bowman, add afterstep. (diff)
NNTP bulk transfer agent.
Submitted by: Dmitry Kohmanyuk <dk@dog.farm.org>
Notes
Notes: svn path=/head/; revision=3421
Diffstat (limited to 'news/nntpbtr')
-rw-r--r--news/nntpbtr/Makefile29
-rw-r--r--news/nntpbtr/distinfo1
-rw-r--r--news/nntpbtr/files/dbz/dbz.3z547
-rw-r--r--news/nntpbtr/files/dbz/dbz.c1766
-rw-r--r--news/nntpbtr/files/dbz/dbz.h32
-rw-r--r--news/nntpbtr/files/patch-aa30
-rw-r--r--news/nntpbtr/files/patch-ab13
-rw-r--r--news/nntpbtr/files/patch-ac52
-rw-r--r--news/nntpbtr/pkg-comment1
-rw-r--r--news/nntpbtr/pkg-descr2
-rw-r--r--news/nntpbtr/pkg-plist2
11 files changed, 2475 insertions, 0 deletions
diff --git a/news/nntpbtr/Makefile b/news/nntpbtr/Makefile
new file mode 100644
index 000000000000..8b7909c288c2
--- /dev/null
+++ b/news/nntpbtr/Makefile
@@ -0,0 +1,29 @@
+# New ports collection makefile for: nntpbtr
+# Version required: 1.7
+# Date created: 1 July 1996
+# Whom: dk
+#
+# $Id$
+#
+
+DISTNAME= nntpbtr-1.7
+CATEGORIES+= news
+MASTER_SITES= ftp://ftp.kiae.su/unix/news/
+EXTRACT_SUFX= .tar.Z
+
+MAINTAINER= dk@farm.org
+
+# "patch" complains otherwise
+post-extract:
+ rm -rf ${WRKSRC}/RCS
+
+pre-build:
+ cd ${FILESDIR} ; pax -rw -l -u dbz ${WRKDIR} || true
+ cd ${WRKDIR}/dbz ; ${CC} ${CFLAGS} -c -I. dbz.c
+
+post-install:
+.if !defined(NOMANCOMPRESS)
+ gzip -9nf ${PREFIX}/man/man1/nntpbtr.1
+.endif
+
+.include <bsd.port.mk>
diff --git a/news/nntpbtr/distinfo b/news/nntpbtr/distinfo
new file mode 100644
index 000000000000..87a452479933
--- /dev/null
+++ b/news/nntpbtr/distinfo
@@ -0,0 +1 @@
+MD5 (nntpbtr-1.7.tar.Z) = 9e4ad41998efa1a042945281a8c0010e
diff --git a/news/nntpbtr/files/dbz/dbz.3z b/news/nntpbtr/files/dbz/dbz.3z
new file mode 100644
index 000000000000..6df25311c701
--- /dev/null
+++ b/news/nntpbtr/files/dbz/dbz.3z
@@ -0,0 +1,547 @@
+.TH DBZ 3Z "3 Feb 1991"
+.BY "C News"
+.SH NAME
+dbminit, fetch, store, dbmclose \- somewhat dbm-compatible database routines
+.br
+dbzfresh, dbzagain, dbzfetch, dbzstore \- database routines
+.br
+dbzsync, dbzsize, dbzincore, dbzcancel, dbzdebug \- database routines
+.SH SYNOPSIS
+.nf
+.B #include <dbz.h>
+.PP
+.B dbminit(base)
+.B char *base;
+.PP
+.B datum
+.B fetch(key)
+.B datum key;
+.PP
+.B store(key, value)
+.B datum key;
+.B datum value;
+.PP
+.B dbmclose()
+.PP
+.B dbzfresh(base, size, fieldsep, cmap, tagmask)
+.B char *base;
+.B long size;
+.B int fieldsep;
+.B int cmap;
+.B long tagmask;
+.PP
+.B dbzagain(base, oldbase)
+.B char *base;
+.B char *oldbase;
+.PP
+.B datum
+.B dbzfetch(key)
+.B datum key;
+.PP
+.B dbzstore(key, value)
+.B datum key;
+.B datum value;
+.PP
+.B dbzsync()
+.PP
+.B long
+.B dbzsize(nentries)
+.B long nentries;
+.PP
+.B dbzincore(newvalue)
+.PP
+.B dbzcancel()
+.PP
+.B dbzdebug(newvalue)
+.SH DESCRIPTION
+These functions provide an indexing system for rapid random access to a
+text file (the
+.I base
+.IR file ).
+Subject to certain constraints, they are call-compatible with
+.IR dbm (3),
+although they also provide some extensions.
+(Note that they are
+.I not
+file-compatible with
+.I dbm
+or any variant thereof.)
+.PP
+In principle,
+.I dbz
+stores key-value pairs, where both key and value are arbitrary sequences
+of bytes, specified to the functions by
+values of type
+.IR datum ,
+typedefed in the header file to be a structure with members
+.I dptr
+(a value of type
+.I char *
+pointing to the bytes)
+and
+.I dsize
+(a value of type
+.I int
+indicating how long the byte sequence is).
+.PP
+In practice,
+.I dbz
+is more restricted than
+.IR dbm .
+A
+.I dbz
+database
+must be an index into a base file,
+with the database
+.IR value s
+being
+.IR fseek (3)
+offsets into the base file.
+Each such
+.I value
+must ``point to'' a place in the base file where the corresponding
+.I key
+sequence is found.
+A key can be no longer than
+.SM DBZMAXKEY
+(a constant defined in the header file) bytes.
+No key can be an initial subsequence of another,
+which in most applications requires that keys be
+either bracketed or terminated in some way (see the
+discussion of the
+.I fieldsep
+parameter of
+.IR dbzfresh ,
+below,
+for a fine point on terminators).
+.PP
+.I Dbminit
+opens a database,
+an index into the base file
+.IR base ,
+consisting of files
+.IB base .dir
+and
+.IB base .pag
+which must already exist.
+(If the database is new, they should be zero-length files.)
+Subsequent accesses go to that database until
+.I dbmclose
+is called to close the database.
+The base file need not exist at the time of the
+.IR dbminit ,
+but it must exist before accesses are attempted.
+.PP
+.I Fetch
+searches the database for the specified
+.IR key ,
+returning the corresponding
+.IR value
+if any.
+.I Store
+stores the
+.IR key - value
+pair in the database.
+.I Store
+will fail unless the database files are writeable.
+See below for a complication arising from case mapping.
+.PP
+.I Dbzfresh
+is a variant of
+.I dbminit
+for creating a new database with more control over details.
+Unlike for
+.IR dbminit ,
+the database files need not exist:
+they will be created if necessary,
+and truncated in any case.
+.PP
+.IR Dbzfresh 's
+.I size
+parameter specifies the size of the first hash table within the database,
+in key-value pairs.
+Performance will be best if
+.I size
+is a prime number and
+the number of key-value pairs stored in the database does not exceed
+about 2/3 of
+.IR size .
+(The
+.I dbzsize
+function, given the expected number of key-value pairs,
+will suggest a database size that meets these criteria.)
+Assuming that an
+.I fseek
+offset is 4 bytes,
+the
+.B .pag
+file will be
+.RI 4* size
+bytes
+(the
+.B .dir
+file is tiny and roughly constant in size)
+until
+the number of key-value pairs exceeds about 80% of
+.IR size .
+(Nothing awful will happen if the database grows beyond 100% of
+.IR size ,
+but accesses will slow down somewhat and the
+.B .pag
+file will grow somewhat.)
+.PP
+.IR Dbzfresh 's
+.I fieldsep
+parameter specifies the field separator in the base file.
+If this is not
+NUL (0), and the last character of a
+.I key
+argument is NUL, that NUL compares equal to either a NUL or a
+.I fieldsep
+in the base file.
+This permits use of NUL to terminate key strings without requiring that
+NULs appear in the base file.
+The
+.I fieldsep
+of a database created with
+.I dbminit
+is the horizontal-tab character.
+.PP
+For use in news systems, various forms of case mapping (e.g. uppercase to
+lowercase) in keys are available.
+The
+.I cmap
+parameter to
+.I dbzfresh
+is a single character specifying which of several mapping algorithms to use.
+Available algorithms are:
+.RS
+.TP
+.B 0
+case-sensitive: no case mapping
+.TP
+.B B
+same as
+.B 0
+.TP
+.B NUL
+same as
+.B 0
+.TP
+.B =
+case-insensitive: uppercase and lowercase equivalent
+.TP
+.B b
+same as
+.B =
+.TP
+.B C
+RFC822 message-ID rules, case-sensitive before `@' (with certain exceptions)
+and case-insensitive after
+.TP
+.B ?
+whatever the local default is, normally
+.B C
+.RE
+.PP
+Mapping algorithm
+.B 0
+(no mapping) is faster than the others and is overwhelmingly the correct
+choice for most applications.
+Unless compatibility constraints interfere, it is more efficient to pre-map
+the keys, storing mapped keys in the base file, than to have
+.I dbz
+do the mapping on every search.
+.PP
+For historical reasons,
+.I fetch
+and
+.I store
+expect their
+.I key
+arguments to be pre-mapped, but expect unmapped keys in the base file.
+.I Dbzfetch
+and
+.I dbzstore
+do the same jobs but handle all case mapping internally,
+so the customer need not worry about it.
+.PP
+.I Dbz
+stores only the database
+.IR value s
+in its files, relying on reference to the base file to confirm a hit on a key.
+References to the base file can be minimized, greatly speeding up searches,
+if a little bit of information about the keys can be stored in the
+.I dbz
+files.
+This is ``free'' if there are some unused bits in an
+.I fseek
+offset,
+so that the offset can be
+.I tagged
+with some information about the key.
+The
+.I tagmask
+parameter of
+.I dbzfresh
+allows specifying the location of unused bits.
+.I Tagmask
+should be a mask with
+one group of
+contiguous
+.B 1
+bits.
+The bits in the mask should
+be unused (0) in
+.I most
+offsets.
+The bit immediately above the mask (the
+.I flag
+bit) should be unused (0) in
+.I all
+offsets;
+.I (dbz)store
+will reject attempts to store a key-value pair in which the
+.I value
+has the flag bit on.
+Apart from this restriction, tagging is invisible to the user.
+As a special case, a
+.I tagmask
+of 1 means ``no tagging'', for use with enormous base files or
+on systems with unusual offset representations.
+.PP
+A
+.I size
+of 0
+given to
+.I dbzfresh
+is synonymous with the local default;
+the normal default is suitable for tables of 90-100,000
+key-value pairs.
+A
+.I cmap
+of 0 (NUL) is synonymous with the character
+.BR 0 ,
+signifying no case mapping
+(note that the character
+.B ?
+specifies the local default mapping,
+normally
+.BR C ).
+A
+.I tagmask
+of 0 is synonymous with the local default tag mask,
+normally 0x7f000000 (specifying the top bit in a 32-bit offset
+as the flag bit, and the next 7 bits as the mask,
+which is suitable for base files up to circa 24MB).
+Calling
+.I dbminit(name)
+with the database files empty is equivalent to calling
+.IR dbzfresh(name,0,'\et','?',0) .
+.PP
+When databases are regenerated periodically, as in news,
+it is simplest to pick the parameters for a new database based on the old one.
+This also permits some memory of past sizes of the old database, so that
+a new database size can be chosen to cover expected fluctuations.
+.I Dbzagain
+is a variant of
+.I dbminit
+for creating a new database as a new generation of an old database.
+The database files for
+.I oldbase
+must exist.
+.I Dbzagain
+is equivalent to calling
+.I dbzfresh
+with the same field separator, case mapping, and tag mask as the old database,
+and a
+.I size
+equal to the result of applying
+.I dbzsize
+to the largest number of entries in the
+.I oldbase
+database and its previous 10 generations.
+.PP
+When many accesses are being done by the same program,
+.I dbz
+is massively faster if its first hash table is in memory.
+If an internal flag is 1,
+an attempt is made to read the table in when
+the database is opened, and
+.I dbmclose
+writes it out to disk again (if it was read successfully and
+has been modified).
+.I Dbzincore
+sets the flag to
+.I newvalue
+(which should be 0 or 1)
+and returns the previous value;
+this does not affect the status of a database that has already been opened.
+The default is 0.
+The attempt to read the table in may fail due to memory shortage;
+in this case
+.I dbz
+quietly falls back on its default behavior.
+.IR Store s
+to an in-memory database are not (in general) written out to the file
+until
+.IR dbmclose
+or
+.IR dbzsync ,
+so if robustness in the presence of crashes
+or concurrent accesses
+is crucial, in-memory databases
+should probably be avoided.
+.PP
+.I Dbzsync
+causes all buffers etc. to be flushed out to the files.
+It is typically used as a precaution against crashes or concurrent accesses
+when a
+.IR dbz -using
+process will be running for a long time.
+It is a somewhat expensive operation,
+especially
+for an in-memory database.
+.PP
+.I Dbzcancel
+cancels any pending writes from buffers.
+This is typically useful only for in-core databases, since writes are
+otherwise done immediately.
+Its main purpose is to let a child process, in the wake of a
+.IR fork ,
+do a
+.I dbmclose
+without writing its parent's data to disk.
+.PP
+If
+.I dbz
+has been compiled with debugging facilities available (which makes it
+bigger and a bit slower),
+.I dbzdebug
+alters the value (and returns the previous value) of an internal flag
+which (when 1; default is 0) causes
+verbose and cryptic debugging output on standard output.
+.PP
+Concurrent reading of databases is fairly safe,
+but there is no (inter)locking,
+so concurrent updating is not.
+.PP
+The database files include a record of the byte order of the processor
+creating the database, and accesses by processors with different byte
+order will work, although they will be slightly slower.
+Byte order is preserved by
+.IR dbzagain .
+However,
+agreement on the size and internal structure of an
+.I fseek
+offset is necessary, as is consensus on
+the character set.
+.PP
+An open database occupies three
+.I stdio
+streams and their corresponding file descriptors;
+a fourth is needed for an in-memory database.
+Memory consumption is negligible (except for
+.I stdio
+buffers) except for in-memory databases.
+.SH SEE ALSO
+dbz(1), dbm(3)
+.SH DIAGNOSTICS
+Functions returning
+.I int
+values return 0 for success, \-1 for failure.
+Functions returning
+.I datum
+values return a value with
+.I dptr
+set to NULL for failure.
+.I Dbminit
+attempts to have
+.I errno
+set plausibly on return, but otherwise this is not guaranteed.
+An
+.I errno
+of
+.B EDOM
+from
+.I dbminit
+indicates that the database did not appear to be in
+.I dbz
+format.
+.SH HISTORY
+The original
+.I dbz
+was written by
+Jon Zeeff (zeeff@b-tech.ann-arbor.mi.us).
+Later contributions by David Butler and Mark Moraes.
+Extensive reworking,
+including this documentation,
+by Henry Spencer (henry@zoo.toronto.edu) as
+part of the C News project.
+Hashing function by Peter Honeyman.
+.SH BUGS
+The
+.I dptr
+members of returned
+.I datum
+values point to static storage which is overwritten by later calls.
+.PP
+Unlike
+.IR dbm ,
+.I dbz
+will misbehave if an existing key-value pair is `overwritten' by
+a new
+.I (dbz)store
+with the same key.
+The user is responsible for avoiding this by using
+.I (dbz)fetch
+first to check for duplicates;
+an internal optimization remembers the result of the
+first search so there is minimal overhead in this.
+.PP
+Waiting until after
+.I dbminit
+to bring the base file into existence
+will fail if
+.IR chdir (2)
+has been used meanwhile.
+.PP
+The RFC822 case mapper implements only a first approximation to the
+hideously-complex RFC822 case rules.
+.PP
+The prime finder in
+.I dbzsize
+is not particularly quick.
+.PP
+Should implement the
+.I dbm
+functions
+.IR delete ,
+.IR firstkey ,
+and
+.IR nextkey .
+.PP
+On C implementations which trap integer overflow,
+.I dbz
+will refuse to
+.I (dbz)store
+an
+.I fseek
+offset equal to the greatest
+representable
+positive number,
+as this would cause overflow in the biased representation used.
+.PP
+.I Dbzagain
+perhaps ought to notice when many offsets
+in the old database were
+too big for
+tagging, and shrink the tag mask to match.
+.PP
+Marking
+.IR dbz 's
+file descriptors
+.RI close-on- exec
+would be a better approach to the problem
+.I dbzcancel
+tries to address, but that's harder to do portably.
diff --git a/news/nntpbtr/files/dbz/dbz.c b/news/nntpbtr/files/dbz/dbz.c
new file mode 100644
index 000000000000..b8b1886e7e0a
--- /dev/null
+++ b/news/nntpbtr/files/dbz/dbz.c
@@ -0,0 +1,1766 @@
+/*
+
+dbz.c V3.2
+
+Copyright 1988 Jon Zeeff (zeeff@b-tech.ann-arbor.mi.us)
+You can use this code in any manner, as long as you leave my name on it
+and don't hold me responsible for any problems with it.
+
+Hacked on by gdb@ninja.UUCP (David Butler); Sun Jun 5 00:27:08 CDT 1988
+
+Various improvments + INCORE by moraes@ai.toronto.edu (Mark Moraes)
+
+Major reworking by Henry Spencer as part of the C News project.
+
+These routines replace dbm as used by the usenet news software
+(it's not a full dbm replacement by any means). It's fast and
+simple. It contains no AT&T code.
+
+In general, dbz's files are 1/20 the size of dbm's. Lookup performance
+is somewhat better, while file creation is spectacularly faster, especially
+if the incore facility is used.
+
+*/
+
+#include <stdio.h>
+#include <sys/types.h>
+#include <string.h>
+#include <ctype.h>
+#include <errno.h>
+#ifndef __STDC__
+extern int errno;
+#endif
+#include <dbz.h>
+
+/*
+ * #ifdef index. "LIA" = "leave it alone unless you know what you're doing".
+ *
+ * FUNNYSEEKS SEEK_SET is not 0, get it from <unistd.h>
+ * INDEX_SIZE backward compatibility with old dbz; avoid using this
+ * NMEMORY number of days of memory for use in sizing new table (LIA)
+ * INCORE backward compatibility with old dbz; use dbzincore() instead
+ * DBZDEBUG enable debugging
+ * DEFSIZE default table size (not as critical as in old dbz)
+ * OLDBNEWS default case mapping as in old B News; set NOBUFFER
+ * BNEWS default case mapping as in current B News; set NOBUFFER
+ * DEFCASE default case-map algorithm selector
+ * NOTAGS fseek offsets are strange, do not do tagging (see below)
+ * NPAGBUF size of .pag buffer, in longs (LIA)
+ * SHISTBUF size of ASCII-file buffer, in bytes (LIA)
+ * MAXRUN length of run which shifts to next table (see below) (LIA)
+ * OVERFLOW long-int arithmetic overflow must be avoided, will trap
+ * NOBUFFER do not buffer hash-table i/o, B News locking is defective
+ */
+
+#ifdef FUNNYSEEKS
+#include <unistd.h>
+#else
+#define SEEK_SET 0
+#endif
+#ifdef OVERFLOW
+#include <limits.h>
+#endif
+
+static int dbzversion = 3; /* for validating .dir file format */
+
+/*
+ * The dbz database exploits the fact that when news stores a <key,value>
+ * tuple, the `value' part is a seek offset into a text file, pointing to
+ * a copy of the `key' part. This avoids the need to store a copy of
+ * the key in the dbz files. However, the text file *must* exist and be
+ * consistent with the dbz files, or things will fail.
+ *
+ * The basic format of the database is a simple hash table containing the
+ * values. A value is stored by indexing into the table using a hash value
+ * computed from the key; collisions are resolved by linear probing (just
+ * search forward for an empty slot, wrapping around to the beginning of
+ * the table if necessary). Linear probing is a performance disaster when
+ * the table starts to get full, so a complication is introduced. The
+ * database is actually one *or more* tables, stored sequentially in the
+ * .pag file, and the length of linear-probe sequences is limited. The
+ * search (for an existing item or an empty slot) always starts in the
+ * first table, and whenever MAXRUN probes have been done in table N,
+ * probing continues in table N+1. This behaves reasonably well even in
+ * cases of massive overflow. There are some other small complications
+ * added, see comments below.
+ *
+ * The table size is fixed for any particular database, but is determined
+ * dynamically when a database is rebuilt. The strategy is to try to pick
+ * the size so the first table will be no more than 2/3 full, that being
+ * slightly before the point where performance starts to degrade. (It is
+ * desirable to be a bit conservative because the overflow strategy tends
+ * to produce files with holes in them, which is a nuisance.)
+ */
+
+/*
+ * The following is for backward compatibility.
+ */
+#ifdef INDEX_SIZE
+#define DEFSIZE INDEX_SIZE
+#endif
+
+/*
+ * ANSI C says an offset into a file is a long, not an off_t, for some
+ * reason. This actually does simplify life a bit, but it's still nice
+ * to have a distinctive name for it. Beware, this is just for readability,
+ * don't try to change this.
+ */
+#define of_t long
+#define SOF (sizeof(of_t))
+
+/*
+ * We assume that unused areas of a binary file are zeros, and that the
+ * bit pattern of `(of_t)0' is all zeros. The alternative is rather
+ * painful file initialization. Note that okayvalue(), if OVERFLOW is
+ * defined, knows what value of an offset would cause overflow.
+ */
+#define VACANT ((of_t)0)
+#define BIAS(o) ((o)+1) /* make any valid of_t non-VACANT */
+#define UNBIAS(o) ((o)-1) /* reverse BIAS() effect */
+
+/*
+ * In a Unix implementation, or indeed any in which an of_t is a byte
+ * count, there are a bunch of high bits free in an of_t. There is a
+ * use for them. Checking a possible hit by looking it up in the base
+ * file is relatively expensive, and the cost can be dramatically reduced
+ * by using some of those high bits to tag the value with a few more bits
+ * of the key's hash. This detects most false hits without the overhead of
+ * seek+read+strcmp. We use the top bit to indicate whether the value is
+ * tagged or not, and don't tag a value which is using the tag bits itself.
+ * We're in trouble if the of_t representation wants to use the top bit.
+ * The actual bitmasks and offset come from the configuration stuff,
+ * which permits fiddling with them as necessary, and also suppressing
+ * them completely (by defining the masks to 0). We build pre-shifted
+ * versions of the masks for efficiency.
+ */
+static of_t tagbits; /* pre-shifted tag mask */
+static of_t taghere; /* pre-shifted tag-enable bit */
+static of_t tagboth; /* tagbits|taghere */
+#define HASTAG(o) ((o)&taghere)
+#define TAG(o) ((o)&tagbits)
+#define NOTAG(o) ((o)&~tagboth)
+#define CANTAG(o) (((o)&tagboth) == 0)
+#define MKTAG(v) (((v)<<conf.tagshift)&tagbits)
+
+/*
+ * A new, from-scratch database, not built as a rebuild of an old one,
+ * needs to know table size, casemap algorithm, and tagging. Normally
+ * the user supplies this info, but there have to be defaults.
+ */
+#ifndef DEFSIZE
+#define DEFSIZE 120011 /* 300007 might be better */
+#endif
+#ifdef OLDBNEWS
+#define DEFCASE '0' /* B2.10 -- no mapping */
+#define NOBUFFER /* B News locking is defective */
+#endif
+#ifdef BNEWS
+#define DEFCASE '=' /* B2.11 -- all mapped */
+#define NOBUFFER /* B News locking is defective */
+#endif
+#ifndef DEFCASE /* C News compatibility is the default */
+#define DEFCASE 'C' /* C News -- RFC822 mapping */
+#endif
+#ifndef NOTAGS
+#define TAGENB 0x80 /* tag enable is top bit, tag is next 7 */
+#define TAGMASK 0x7f
+#define TAGSHIFT 24
+#else
+#define TAGENB 0 /* no tags */
+#define TAGMASK 0
+#define TAGSHIFT 0
+#endif
+
+/*
+ * We read configuration info from the .dir file into this structure,
+ * so we can avoid wired-in assumptions for an existing database.
+ *
+ * Among the info is a record of recent peak usages, so that a new table
+ * size can be chosen intelligently when rebuilding. 10 is a good
+ * number of usages to keep, since news displays marked fluctuations
+ * in volume on a 7-day cycle.
+ */
+struct dbzconfig {
+ int olddbz; /* .dir file empty but .pag not? */
+ of_t tsize; /* table size */
+# ifndef NMEMORY
+# define NMEMORY 10 /* # days of use info to remember */
+# endif
+# define NUSEDS (1+NMEMORY)
+ of_t used[NUSEDS]; /* entries used today, yesterday, ... */
+ int valuesize; /* size of table values, == SOF */
+ int bytemap[SOF]; /* byte-order map */
+ char casemap; /* case-mapping algorithm (see cipoint()) */
+ char fieldsep; /* field separator in base file, if any */
+ of_t tagenb; /* unshifted tag-enable bit */
+ of_t tagmask; /* unshifted tag mask */
+ int tagshift; /* shift count for tagmask and tagenb */
+};
+static struct dbzconfig conf;
+static int getconf();
+static long getno();
+static int putconf();
+static void mybytemap();
+static of_t bytemap();
+
+/*
+ * For a program that makes many, many references to the database, it
+ * is a large performance win to keep the table in core, if it will fit.
+ * Note that this does hurt robustness in the event of crashes, and
+ * dbmclose() *must* be called to flush the in-core database to disk.
+ * The code is prepared to deal with the possibility that there isn't
+ * enough memory. There *is* an assumption that a size_t is big enough
+ * to hold the size (in bytes) of one table, so dbminit() tries to figure
+ * out whether this is possible first.
+ *
+ * The preferred way to ask for an in-core table is to do dbzincore(1)
+ * before dbminit(). The default is not to do it, although -DINCORE
+ * overrides this for backward compatibility with old dbz.
+ *
+ * We keep only the first table in core. This greatly simplifies the
+ * code, and bounds memory demand. Furthermore, doing this is a large
+ * performance win even in the event of massive overflow.
+ */
+#ifdef INCORE
+static int incore = 1;
+#else
+static int incore = 0;
+#endif
+
+/*
+ * Stdio buffer for .pag reads. Buffering more than about 16 does not help
+ * significantly at the densities we try to maintain, and the much larger
+ * buffers that most stdios default to are much more expensive to fill.
+ * With small buffers, stdio is performance-competitive with raw read(),
+ * and it's much more portable.
+ */
+#ifndef NPAGBUF
+#define NPAGBUF 16
+#endif
+#ifndef NOBUFFER
+#ifdef _IOFBF
+static of_t pagbuf[NPAGBUF]; /* only needed if !NOBUFFER && _IOFBF */
+#endif
+#endif
+
+/*
+ * Stdio buffer for base-file reads. Message-IDs (all news ever needs to
+ * read) are essentially never longer than 64 bytes, and the typical stdio
+ * buffer is so much larger that it is much more expensive to fill.
+ */
+#ifndef SHISTBUF
+#define SHISTBUF 64
+#endif
+#ifdef _IOFBF
+static char basebuf[SHISTBUF]; /* only needed if _IOFBF exists */
+#endif
+
+/*
+ * Data structure for recording info about searches.
+ */
+struct searcher {
+ of_t place; /* current location in file */
+ int tabno; /* which table we're in */
+ int run; /* how long we'll stay in this table */
+# ifndef MAXRUN
+# define MAXRUN 100
+# endif
+ long hash; /* the key's hash code (for optimization) */
+ of_t tag; /* tag we are looking for */
+ int seen; /* have we examined current location? */
+ int aborted; /* has i/o error aborted search? */
+};
+static void start();
+#define FRESH ((struct searcher *)NULL)
+static of_t search();
+#define NOTFOUND ((of_t)-1)
+static int okayvalue();
+static int set();
+
+/*
+ * Arguably the searcher struct for a given routine ought to be local to
+ * it, but a fetch() is very often immediately followed by a store(), and
+ * in some circumstances it is a useful performance win to remember where
+ * the fetch() completed. So we use a global struct and remember whether
+ * it is current.
+ */
+static struct searcher srch;
+static struct searcher *prevp; /* &srch or FRESH */
+
+/* byte-ordering stuff */
+static int mybmap[SOF]; /* my byte order (see mybytemap()) */
+static int bytesame; /* is database order same as mine? */
+#define MAPIN(o) ((bytesame) ? (o) : bytemap((o), conf.bytemap, mybmap))
+#define MAPOUT(o) ((bytesame) ? (o) : bytemap((o), mybmap, conf.bytemap))
+
+/*
+ * The double parentheses needed to make this work are ugly, but the
+ * alternative (under most compilers) is to pack around 2K of unused
+ * strings -- there's just no way to get rid of them.
+ */
+static int debug; /* controlled by dbzdebug() */
+#ifdef DBZDEBUG
+#define DEBUG(args) if (debug) { (void) printf args ; }
+#else
+#define DEBUG(args) ;
+#endif
+
+/* externals used */
+extern char *malloc();
+extern char *calloc();
+extern void free(); /* ANSI C; some old implementations say int */
+extern int atoi();
+extern long atol();
+
+/* misc. forwards */
+static long hash();
+static void crcinit();
+static char *cipoint();
+static char *mapcase();
+static int isprime();
+static FILE *latebase();
+
+/* file-naming stuff */
+static char dir[] = ".dir";
+static char pag[] = ".pag";
+static char *enstring();
+
+/* central data structures */
+static FILE *basef; /* descriptor for base file */
+static char *basefname; /* name for not-yet-opened base file */
+static FILE *dirf; /* descriptor for .dir file */
+static int dirronly; /* dirf open read-only? */
+static FILE *pagf = NULL; /* descriptor for .pag file */
+static of_t pagpos; /* posn in pagf; only search may set != -1 */
+static int pagronly; /* pagf open read-only? */
+static of_t *corepag; /* incore version of .pag file, if any */
+static FILE *bufpagf; /* well-buffered pagf, for incore rewrite */
+static of_t *getcore();
+static int putcore();
+static int written; /* has a store() been done? */
+
+/*
+ - dbzfresh - set up a new database, no historical info
+ */
+int /* 0 success, -1 failure */
+dbzfresh(name, size, fs, cmap, tagmask)
+char *name; /* base name; .dir and .pag must exist */
+long size; /* table size (0 means default) */
+int fs; /* field-separator character in base file */
+int cmap; /* case-map algorithm (0 means default) */
+of_t tagmask; /* 0 default, 1 no tags */
+{
+ register char *fn;
+ struct dbzconfig c;
+ register of_t m;
+ register FILE *f;
+
+ if (pagf != NULL) {
+ DEBUG(("dbzfresh: database already open\n"));
+ return(-1);
+ }
+ if (size != 0 && size < 2) {
+ DEBUG(("dbzfresh: preposterous size (%ld)\n", size));
+ return(-1);
+ }
+
+ /* get default configuration */
+ if (getconf((FILE *)NULL, (FILE *)NULL, &c) < 0)
+ return(-1); /* "can't happen" */
+
+ /* and mess with it as specified */
+ if (size != 0)
+ c.tsize = size;
+ c.fieldsep = fs;
+ switch (cmap) {
+ case 0:
+ case '0':
+ case 'B': /* 2.10 compat */
+ c.casemap = '0'; /* '\0' nicer, but '0' printable! */
+ break;
+ case '=':
+ case 'b': /* 2.11 compat */
+ c.casemap = '=';
+ break;
+ case 'C':
+ c.casemap = 'C';
+ break;
+ case '?':
+ c.casemap = DEFCASE;
+ break;
+ default:
+ DEBUG(("dbzfresh case map `%c' unknown\n", cmap));
+ return(-1);
+ break;
+ }
+ switch (tagmask) {
+ case 0: /* default */
+ break;
+ case 1: /* no tags */
+ c.tagshift = 0;
+ c.tagmask = 0;
+ c.tagenb = 0;
+ break;
+ default:
+ m = tagmask;
+ c.tagshift = 0;
+ while (!(m&01)) {
+ m >>= 1;
+ c.tagshift++;
+ }
+ c.tagmask = m;
+ c.tagenb = (m << 1) & ~m;
+ break;
+ }
+
+ /* write it out */
+ fn = enstring(name, dir);
+ if (fn == NULL)
+ return(-1);
+ f = fopen(fn, "w");
+ free(fn);
+ if (f == NULL) {
+ DEBUG(("dbzfresh: unable to write config\n"));
+ return(-1);
+ }
+ if (putconf(f, &c) < 0) {
+ (void) fclose(f);
+ return(-1);
+ }
+ if (fclose(f) == EOF) {
+ DEBUG(("dbzfresh: fclose failure\n"));
+ return(-1);
+ }
+
+ /* create/truncate .pag */
+ fn = enstring(name, pag);
+ if (fn == NULL)
+ return(-1);
+ f = fopen(fn, "w");
+ free(fn);
+ if (f == NULL) {
+ DEBUG(("dbzfresh: unable to create/truncate .pag file\n"));
+ return(-1);
+ } else
+ (void) fclose(f);
+
+ /* and punt to dbminit for the hard work */
+ return(dbminit(name));
+}
+
+/*
+ - dbzsize - what's a good table size to hold this many entries?
+ */
+long
+dbzsize(contents)
+long contents; /* 0 means what's the default */
+{
+ register long n;
+
+ if (contents <= 0) { /* foulup or default inquiry */
+ DEBUG(("dbzsize: preposterous input (%ld)\n", contents));
+ return(DEFSIZE);
+ }
+ n = (contents/2)*3; /* try to keep table at most 2/3 full */
+ if (!(n&01)) /* make it odd */
+ n++;
+ DEBUG(("dbzsize: tentative size %ld\n", n));
+ while (!isprime(n)) /* and look for a prime */
+ n += 2;
+ DEBUG(("dbzsize: final size %ld\n", n));
+
+ return(n);
+}
+
+/*
+ - isprime - is a number prime?
+ *
+ * This is not a terribly efficient approach.
+ */
+static int /* predicate */
+isprime(x)
+register long x;
+{
+ static int quick[] = { 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 0 };
+ register int *ip;
+ register long div;
+ register long stop;
+
+ /* hit the first few primes quickly to eliminate easy ones */
+ /* this incidentally prevents ridiculously small tables */
+ for (ip = quick; (div = *ip) != 0; ip++)
+ if (x%div == 0) {
+ DEBUG(("isprime: quick result on %ld\n", (long)x));
+ return(0);
+ }
+
+ /* approximate square root of x */
+ for (stop = x; x/stop < stop; stop >>= 1)
+ continue;
+ stop <<= 1;
+
+ /* try odd numbers up to stop */
+ for (div = *--ip; div < stop; div += 2)
+ if (x%div == 0)
+ return(0);
+
+ return(1);
+}
+
+/*
+ - dbzagain - set up a new database to be a rebuild of an old one
+ */
+int /* 0 success, -1 failure */
+dbzagain(name, oldname)
+char *name; /* base name; .dir and .pag must exist */
+char *oldname; /* base name; all must exist */
+{
+ register char *fn;
+ struct dbzconfig c;
+ register int i;
+ register long top;
+ register FILE *f;
+ register int newtable;
+ register of_t newsize;
+
+ if (pagf != NULL) {
+ DEBUG(("dbzagain: database already open\n"));
+ return(-1);
+ }
+
+ /* pick up the old configuration */
+ fn = enstring(oldname, dir);
+ if (fn == NULL)
+ return(-1);
+ f = fopen(fn, "r");
+ free(fn);
+ if (f == NULL) {
+ DEBUG(("dbzagain: cannot open old .dir file\n"));
+ return(-1);
+ }
+ i = getconf(f, (FILE *)NULL, &c);
+ (void) fclose(f);
+ if (i < 0) {
+ DEBUG(("dbzagain: getconf failed\n"));
+ return(-1);
+ }
+
+ /* tinker with it */
+ top = 0;
+ newtable = 0;
+ for (i = 0; i < NUSEDS; i++) {
+ if (top < c.used[i])
+ top = c.used[i];
+ if (c.used[i] == 0)
+ newtable = 1; /* hasn't got full usage history yet */
+ }
+ if (top == 0) {
+ DEBUG(("dbzagain: old table has no contents!\n"));
+ newtable = 1;
+ }
+ for (i = NUSEDS-1; i > 0; i--)
+ c.used[i] = c.used[i-1];
+ c.used[0] = 0;
+ newsize = dbzsize(top);
+ if (!newtable || newsize > c.tsize) /* don't shrink new table */
+ c.tsize = newsize;
+
+ /* write it out */
+ fn = enstring(name, dir);
+ if (fn == NULL)
+ return(-1);
+ f = fopen(fn, "w");
+ free(fn);
+ if (f == NULL) {
+ DEBUG(("dbzagain: unable to write new .dir\n"));
+ return(-1);
+ }
+ i = putconf(f, &c);
+ (void) fclose(f);
+ if (i < 0) {
+ DEBUG(("dbzagain: putconf failed\n"));
+ return(-1);
+ }
+
+ /* create/truncate .pag */
+ fn = enstring(name, pag);
+ if (fn == NULL)
+ return(-1);
+ f = fopen(fn, "w");
+ free(fn);
+ if (f == NULL) {
+ DEBUG(("dbzagain: unable to create/truncate .pag file\n"));
+ return(-1);
+ } else
+ (void) fclose(f);
+
+ /* and let dbminit do the work */
+ return(dbminit(name));
+}
+
+/*
+ - dbminit - open a database, creating it (using defaults) if necessary
+ *
+ * We try to leave errno set plausibly, to the extent that underlying
+ * functions permit this, since many people consult it if dbminit() fails.
+ */
+int /* 0 success, -1 failure */
+dbminit(name)
+char *name;
+{
+ register int i;
+ register size_t s;
+ register char *dirfname;
+ register char *pagfname;
+
+ if (pagf != NULL) {
+ DEBUG(("dbminit: dbminit already called once\n"));
+ errno = 0;
+ return(-1);
+ }
+
+ /* open the .dir file */
+ dirfname = enstring(name, dir);
+ if (dirfname == NULL)
+ return(-1);
+ dirf = fopen(dirfname, "r+");
+ if (dirf == NULL) {
+ dirf = fopen(dirfname, "r");
+ dirronly = 1;
+ } else
+ dirronly = 0;
+ free(dirfname);
+ if (dirf == NULL) {
+ DEBUG(("dbminit: can't open .dir file\n"));
+ return(-1);
+ }
+
+ /* open the .pag file */
+ pagfname = enstring(name, pag);
+ if (pagfname == NULL) {
+ (void) fclose(dirf);
+ return(-1);
+ }
+ pagf = fopen(pagfname, "r+b");
+ if (pagf == NULL) {
+ pagf = fopen(pagfname, "rb");
+ if (pagf == NULL) {
+ DEBUG(("dbminit: .pag open failed\n"));
+ (void) fclose(dirf);
+ free(pagfname);
+ return(-1);
+ }
+ pagronly = 1;
+ } else if (dirronly)
+ pagronly = 1;
+ else
+ pagronly = 0;
+#ifdef NOBUFFER
+ /*
+ * B News does not do adequate locking on its database accesses.
+ * Why it doesn't get into trouble using dbm is a mystery. In any
+ * case, doing unbuffered i/o does not cure the problem, but does
+ * enormously reduce its incidence.
+ */
+ (void) setbuf(pagf, (char *)NULL);
+#else
+#ifdef _IOFBF
+ (void) setvbuf(pagf, (char *)pagbuf, _IOFBF, sizeof(pagbuf));
+#endif
+#endif
+ pagpos = -1;
+ /* don't free pagfname, need it below */
+
+ /* open the base file */
+ basef = fopen(name, "r");
+ if (basef == NULL) {
+ DEBUG(("dbminit: basefile open failed\n"));
+ basefname = enstring(name, "");
+ if (basefname == NULL) {
+ (void) fclose(pagf);
+ (void) fclose(dirf);
+ free(pagfname);
+ pagf = NULL;
+ return(-1);
+ }
+ } else
+ basefname = NULL;
+#ifdef _IOFBF
+ if (basef != NULL)
+ (void) setvbuf(basef, basebuf, _IOFBF, sizeof(basebuf));
+#endif
+
+ /* pick up configuration */
+ if (getconf(dirf, pagf, &conf) < 0) {
+ DEBUG(("dbminit: getconf failure\n"));
+ (void) fclose(basef);
+ (void) fclose(pagf);
+ (void) fclose(dirf);
+ free(pagfname);
+ pagf = NULL;
+ errno = EDOM; /* kind of a kludge, but very portable */
+ return(-1);
+ }
+ tagbits = conf.tagmask << conf.tagshift;
+ taghere = conf.tagenb << conf.tagshift;
+ tagboth = tagbits | taghere;
+ mybytemap(mybmap);
+ bytesame = 1;
+ for (i = 0; i < SOF; i++)
+ if (mybmap[i] != conf.bytemap[i])
+ bytesame = 0;
+
+ /* get first table into core, if it looks desirable and feasible */
+ s = (size_t)conf.tsize * SOF;
+ if (incore && (of_t)(s/SOF) == conf.tsize) {
+ bufpagf = fopen(pagfname, (pagronly) ? "rb" : "r+b");
+ if (bufpagf != NULL)
+ corepag = getcore(bufpagf);
+ } else {
+ bufpagf = NULL;
+ corepag = NULL;
+ }
+ free(pagfname);
+
+ /* misc. setup */
+ crcinit();
+ written = 0;
+ prevp = FRESH;
+ DEBUG(("dbminit: succeeded\n"));
+ return(0);
+}
+
+/*
+ - enstring - concatenate two strings into a malloced area
+ */
+static char * /* NULL if malloc fails */
+enstring(s1, s2)
+char *s1;
+char *s2;
+{
+ register char *p;
+
+ p = malloc((size_t)strlen(s1) + (size_t)strlen(s2) + 1);
+ if (p != NULL) {
+ (void) strcpy(p, s1);
+ (void) strcat(p, s2);
+ } else {
+ DEBUG(("enstring(%s, %s) out of memory\n", s1, s2));
+ }
+ return(p);
+}
+
+/*
+ - dbmclose - close a database
+ */
+int
+dbmclose()
+{
+ register int ret = 0;
+
+ if (pagf == NULL) {
+ DEBUG(("dbmclose: not opened!\n"));
+ return(-1);
+ }
+
+ if (fclose(pagf) == EOF) {
+ DEBUG(("dbmclose: fclose(pagf) failed\n"));
+ ret = -1;
+ }
+ pagf = basef; /* ensure valid pointer; dbzsync checks it */
+ if (dbzsync() < 0)
+ ret = -1;
+ if (bufpagf != NULL && fclose(bufpagf) == EOF) {
+ DEBUG(("dbmclose: fclose(bufpagf) failed\n"));
+ ret = -1;
+ }
+ if (corepag != NULL)
+ free((char *)corepag);
+ corepag = NULL;
+ if (fclose(basef) == EOF) {
+ DEBUG(("dbmclose: fclose(basef) failed\n"));
+ ret = -1;
+ }
+ if (basefname != NULL)
+ free(basefname);
+ basef = NULL;
+ pagf = NULL;
+ if (fclose(dirf) == EOF) {
+ DEBUG(("dbmclose: fclose(dirf) failed\n"));
+ ret = -1;
+ }
+
+ DEBUG(("dbmclose: %s\n", (ret == 0) ? "succeeded" : "failed"));
+ return(ret);
+}
+
+/*
+ - dbzsync - push all in-core data out to disk
+ */
+int
+dbzsync()
+{
+ register int ret = 0;
+
+ if (pagf == NULL) {
+ DEBUG(("dbzsync: not opened!\n"));
+ return(-1);
+ }
+ if (!written)
+ return(0);
+
+ if (corepag != NULL) {
+ if (putcore(corepag, bufpagf) < 0) {
+ DEBUG(("dbzsync: putcore failed\n"));
+ ret = -1;
+ }
+ }
+ if (!conf.olddbz)
+ if (putconf(dirf, &conf) < 0)
+ ret = -1;
+
+ DEBUG(("dbzsync: %s\n", (ret == 0) ? "succeeded" : "failed"));
+ return(ret);
+}
+
+/*
+ - dbzcancel - cancel writing of in-core data
+ * Mostly for use from child processes.
+ * Note that we don't need to futz around with stdio buffers, because we
+ * always fflush them immediately anyway and so they never have stale data.
+ */
+int
+dbzcancel()
+{
+ if (pagf == NULL) {
+ DEBUG(("dbzcancel: not opened!\n"));
+ return(-1);
+ }
+
+ written = 0;
+ return(0);
+}
+
+/*
+ - dbzfetch - fetch() with case mapping built in
+ */
+datum
+dbzfetch(key)
+datum key;
+{
+ char buffer[DBZMAXKEY + 1];
+ datum mappedkey;
+ register size_t keysize;
+
+ DEBUG(("dbzfetch: (%s)\n", key.dptr));
+
+ /* Key is supposed to be less than DBZMAXKEY */
+ keysize = key.dsize;
+ if (keysize >= DBZMAXKEY) {
+ keysize = DBZMAXKEY;
+ DEBUG(("keysize is %d - truncated to %d\n", key.dsize, DBZMAXKEY));
+ }
+
+ mappedkey.dptr = mapcase(buffer, key.dptr, keysize);
+ buffer[keysize] = '\0'; /* just a debug aid */
+ mappedkey.dsize = keysize;
+
+ return(fetch(mappedkey));
+}
+
+/*
+ - fetch - get an entry from the database
+ *
+ * Disgusting fine point, in the name of backward compatibility: if the
+ * last character of "key" is a NUL, that character is (effectively) not
+ * part of the comparison against the stored keys.
+ */
+datum /* dptr NULL, dsize 0 means failure */
+fetch(key)
+datum key;
+{
+ char buffer[DBZMAXKEY + 1];
+ static of_t key_ptr; /* return value points here */
+ datum output;
+ register size_t keysize;
+ register size_t cmplen;
+ register char *sepp;
+
+ DEBUG(("fetch: (%s)\n", key.dptr));
+ output.dptr = NULL;
+ output.dsize = 0;
+ prevp = FRESH;
+
+ /* Key is supposed to be less than DBZMAXKEY */
+ keysize = key.dsize;
+ if (keysize >= DBZMAXKEY) {
+ keysize = DBZMAXKEY;
+ DEBUG(("keysize is %d - truncated to %d\n", key.dsize, DBZMAXKEY));
+ }
+
+ if (pagf == NULL) {
+ DEBUG(("fetch: database not open!\n"));
+ return(output);
+ } else if (basef == NULL) { /* basef didn't exist yet */
+ basef = latebase();
+ if (basef == NULL)
+ return(output);
+ }
+
+ cmplen = keysize;
+ sepp = &conf.fieldsep;
+ if (key.dptr[keysize-1] == '\0') {
+ cmplen--;
+ sepp = &buffer[keysize-1];
+ }
+ start(&srch, &key, FRESH);
+ while ((key_ptr = search(&srch)) != NOTFOUND) {
+ DEBUG(("got 0x%lx\n", key_ptr));
+
+ /* fetch the key */
+ if (fseek(basef, key_ptr, SEEK_SET) != 0) {
+ DEBUG(("fetch: seek failed\n"));
+ return(output);
+ }
+ if (fread(buffer, 1, keysize, basef) != keysize) {
+ DEBUG(("fetch: read failed\n"));
+ return(output);
+ }
+
+ /* try it */
+ buffer[keysize] = '\0'; /* terminated for DEBUG */
+ (void) mapcase(buffer, buffer, keysize);
+ DEBUG(("fetch: buffer (%s) looking for (%s) size = %d\n",
+ buffer, key.dptr, keysize));
+ if (memcmp(key.dptr, buffer, cmplen) == 0 &&
+ (*sepp == conf.fieldsep || *sepp == '\0')) {
+ /* we found it */
+ output.dptr = (char *)&key_ptr;
+ output.dsize = SOF;
+ DEBUG(("fetch: successful\n"));
+ return(output);
+ }
+ }
+
+ /* we didn't find it */
+ DEBUG(("fetch: failed\n"));
+ prevp = &srch; /* remember where we stopped */
+ return(output);
+}
+
+/*
+ - latebase - try to open a base file that wasn't there at the start
+ */
+static FILE *
+latebase()
+{
+ register FILE *it;
+
+ if (basefname == NULL) {
+ DEBUG(("latebase: name foulup\n"));
+ return(NULL);
+ }
+ it = fopen(basefname, "r");
+ if (it == NULL) {
+ DEBUG(("latebase: still can't open base\n"));
+ } else {
+ DEBUG(("latebase: late open succeeded\n"));
+ free(basefname);
+ basefname = NULL;
+#ifdef _IOFBF
+ (void) setvbuf(it, basebuf, _IOFBF, sizeof(basebuf));
+#endif
+ }
+ return(it);
+}
+
+/*
+ - dbzstore - store() with case mapping built in
+ */
+int
+dbzstore(key, data)
+datum key;
+datum data;
+{
+ char buffer[DBZMAXKEY + 1];
+ datum mappedkey;
+ register size_t keysize;
+
+ DEBUG(("dbzstore: (%s)\n", key.dptr));
+
+ /* Key is supposed to be less than DBZMAXKEY */
+ keysize = key.dsize;
+ if (keysize >= DBZMAXKEY) {
+ DEBUG(("dbzstore: key size too big (%d)\n", key.dsize));
+ return(-1);
+ }
+
+ mappedkey.dptr = mapcase(buffer, key.dptr, keysize);
+ buffer[keysize] = '\0'; /* just a debug aid */
+ mappedkey.dsize = keysize;
+
+ return(store(mappedkey, data));
+}
+
+/*
+ - store - add an entry to the database
+ */
+int /* 0 success, -1 failure */
+store(key, data)
+datum key;
+datum data;
+{
+ of_t value;
+
+ if (pagf == NULL) {
+ DEBUG(("store: database not open!\n"));
+ return(-1);
+ } else if (basef == NULL) { /* basef didn't exist yet */
+ basef = latebase();
+ if (basef == NULL)
+ return(-1);
+ }
+ if (pagronly) {
+ DEBUG(("store: database open read-only\n"));
+ return(-1);
+ }
+ if (data.dsize != SOF) {
+ DEBUG(("store: value size wrong (%d)\n", data.dsize));
+ return(-1);
+ }
+ if (key.dsize >= DBZMAXKEY) {
+ DEBUG(("store: key size too big (%d)\n", key.dsize));
+ return(-1);
+ }
+
+ /* copy the value in to ensure alignment */
+ (void) memcpy((char *)&value, data.dptr, SOF);
+ DEBUG(("store: (%s, %ld)\n", key.dptr, (long)value));
+ if (!okayvalue(value)) {
+ DEBUG(("store: reserved bit or overflow in 0x%lx\n", value));
+ return(-1);
+ }
+
+ /* find the place, exploiting previous search if possible */
+ start(&srch, &key, prevp);
+ while (search(&srch) != NOTFOUND)
+ continue;
+
+ prevp = FRESH;
+ conf.used[0]++;
+ DEBUG(("store: used count %ld\n", conf.used[0]));
+ written = 1;
+ return(set(&srch, value));
+}
+
+/*
+ - dbzincore - control attempts to keep .pag file in core
+ */
+int /* old setting */
+dbzincore(value)
+int value;
+{
+ register int old = incore;
+
+ incore = value;
+ return(old);
+}
+
+/*
+ - getconf - get configuration from .dir file
+ */
+static int /* 0 success, -1 failure */
+getconf(df, pf, cp)
+register FILE *df; /* NULL means just give me the default */
+register FILE *pf; /* NULL means don't care about .pag */
+register struct dbzconfig *cp;
+{
+ register int c;
+ register int i;
+ int err = 0;
+
+ c = (df != NULL) ? getc(df) : EOF;
+ if (c == EOF) { /* empty file, no configuration known */
+ cp->olddbz = 0;
+ if (df != NULL && pf != NULL && getc(pf) != EOF)
+ cp->olddbz = 1;
+ cp->tsize = DEFSIZE;
+ cp->fieldsep = '\t';
+ for (i = 0; i < NUSEDS; i++)
+ cp->used[i] = 0;
+ cp->valuesize = SOF;
+ mybytemap(cp->bytemap);
+ cp->casemap = DEFCASE;
+ cp->tagenb = TAGENB;
+ cp->tagmask = TAGMASK;
+ cp->tagshift = TAGSHIFT;
+ DEBUG(("getconf: defaults (%ld, %c, (0x%lx/0x%lx<<%d))\n",
+ cp->tsize, cp->casemap, cp->tagenb,
+ cp->tagmask, cp->tagshift));
+ return(0);
+ }
+ (void) ungetc(c, df);
+
+ /* first line, the vital stuff */
+ if (getc(df) != 'd' || getc(df) != 'b' || getc(df) != 'z')
+ err = -1;
+ if (getno(df, &err) != dbzversion)
+ err = -1;
+ cp->tsize = getno(df, &err);
+ cp->fieldsep = getno(df, &err);
+ while ((c = getc(df)) == ' ')
+ continue;
+ cp->casemap = c;
+ cp->tagenb = getno(df, &err);
+ cp->tagmask = getno(df, &err);
+ cp->tagshift = getno(df, &err);
+ cp->valuesize = getno(df, &err);
+ if (cp->valuesize != SOF) {
+ DEBUG(("getconf: wrong of_t size (%d)\n", cp->valuesize));
+ err = -1;
+ cp->valuesize = SOF; /* to protect the loops below */
+ }
+ for (i = 0; i < cp->valuesize; i++)
+ cp->bytemap[i] = getno(df, &err);
+ if (getc(df) != '\n')
+ err = -1;
+ DEBUG(("size %ld, sep %d, cmap %c, tags 0x%lx/0x%lx<<%d, ", cp->tsize,
+ cp->fieldsep, cp->casemap, cp->tagenb, cp->tagmask,
+ cp->tagshift));
+ DEBUG(("bytemap (%d)", cp->valuesize));
+ for (i = 0; i < cp->valuesize; i++) {
+ DEBUG((" %d", cp->bytemap[i]));
+ }
+ DEBUG(("\n"));
+
+ /* second line, the usages */
+ for (i = 0; i < NUSEDS; i++)
+ cp->used[i] = getno(df, &err);
+ if (getc(df) != '\n')
+ err = -1;
+ DEBUG(("used %ld %ld %ld...\n", cp->used[0], cp->used[1], cp->used[2]));
+
+ if (err < 0) {
+ DEBUG(("getconf error\n"));
+ return(-1);
+ }
+ return(0);
+}
+
+/*
+ - getno - get a long
+ */
+static long
+getno(f, ep)
+FILE *f;
+int *ep;
+{
+ register char *p;
+# define MAXN 50
+ char getbuf[MAXN];
+ register int c;
+
+ while ((c = getc(f)) == ' ')
+ continue;
+ if (c == EOF || c == '\n') {
+ DEBUG(("getno: missing number\n"));
+ *ep = -1;
+ return(0);
+ }
+ p = getbuf;
+ *p++ = c;
+ while ((c = getc(f)) != EOF && c != '\n' && c != ' ')
+ if (p < &getbuf[MAXN-1])
+ *p++ = c;
+ if (c == EOF) {
+ DEBUG(("getno: EOF\n"));
+ *ep = -1;
+ } else
+ (void) ungetc(c, f);
+ *p = '\0';
+
+ if (strspn(getbuf, "-1234567890") != strlen(getbuf)) {
+ DEBUG(("getno: `%s' non-numeric\n", getbuf));
+ *ep = -1;
+ }
+ return(atol(getbuf));
+}
+
+/*
+ - putconf - write configuration to .dir file
+ */
+static int /* 0 success, -1 failure */
+putconf(f, cp)
+register FILE *f;
+register struct dbzconfig *cp;
+{
+ register int i;
+ register int ret = 0;
+
+ if (fseek(f, (of_t)0, SEEK_SET) != 0) {
+ DEBUG(("fseek failure in putconf\n"));
+ ret = -1;
+ }
+ fprintf(f, "dbz %d %ld %d %c %ld %ld %d %d", dbzversion, cp->tsize,
+ cp->fieldsep, cp->casemap, cp->tagenb,
+ cp->tagmask, cp->tagshift, cp->valuesize);
+ for (i = 0; i < cp->valuesize; i++)
+ fprintf(f, " %d", cp->bytemap[i]);
+ fprintf(f, "\n");
+ for (i = 0; i < NUSEDS; i++)
+ fprintf(f, "%ld%c", cp->used[i], (i < NUSEDS-1) ? ' ' : '\n');
+
+ (void) fflush(f);
+ if (ferror(f))
+ ret = -1;
+
+ DEBUG(("putconf status %d\n", ret));
+ return(ret);
+}
+
+/*
+ - getcore - try to set up an in-core copy of .pag file
+ */
+static of_t * /* pointer to copy, or NULL */
+getcore(f)
+FILE *f;
+{
+ register of_t *p;
+ register size_t i;
+ register size_t nread;
+ register char *it;
+
+ it = malloc((size_t)conf.tsize * SOF);
+ if (it == NULL) {
+ DEBUG(("getcore: malloc failed\n"));
+ return(NULL);
+ }
+
+ nread = fread(it, SOF, (size_t)conf.tsize, f);
+ if (ferror(f)) {
+ DEBUG(("getcore: read failed\n"));
+ free(it);
+ return(NULL);
+ }
+
+ p = (of_t *)it + nread;
+ i = (size_t)conf.tsize - nread;
+ while (i-- > 0)
+ *p++ = VACANT;
+ return((of_t *)it);
+}
+
+/*
+ - putcore - try to rewrite an in-core table
+ */
+static int /* 0 okay, -1 fail */
+putcore(tab, f)
+of_t *tab;
+FILE *f;
+{
+ if (fseek(f, (of_t)0, SEEK_SET) != 0) {
+ DEBUG(("fseek failure in putcore\n"));
+ return(-1);
+ }
+ (void) fwrite((char *)tab, SOF, (size_t)conf.tsize, f);
+ (void) fflush(f);
+ return((ferror(f)) ? -1 : 0);
+}
+
+/*
+ - start - set up to start or restart a search
+ */
+static void
+start(sp, kp, osp)
+register struct searcher *sp;
+register datum *kp;
+register struct searcher *osp; /* may be FRESH, i.e. NULL */
+{
+ register long h;
+
+ h = hash(kp->dptr, kp->dsize);
+ if (osp != FRESH && osp->hash == h) {
+ if (sp != osp)
+ *sp = *osp;
+ DEBUG(("search restarted\n"));
+ } else {
+ sp->hash = h;
+ sp->tag = MKTAG(h / conf.tsize);
+ DEBUG(("tag 0x%lx\n", sp->tag));
+ sp->place = h % conf.tsize;
+ sp->tabno = 0;
+ sp->run = (conf.olddbz) ? conf.tsize : MAXRUN;
+ sp->aborted = 0;
+ }
+ sp->seen = 0;
+}
+
+/*
+ - search - conduct part of a search
+ */
+static of_t /* NOTFOUND if we hit VACANT or error */
+search(sp)
+register struct searcher *sp;
+{
+ register of_t dest;
+ register of_t value;
+ of_t val; /* buffer for value (can't fread register) */
+ register of_t place;
+
+ if (sp->aborted)
+ return(NOTFOUND);
+
+ for (;;) {
+ /* determine location to be examined */
+ place = sp->place;
+ if (sp->seen) {
+ /* go to next location */
+ if (--sp->run <= 0) {
+ sp->tabno++;
+ sp->run = MAXRUN;
+ }
+ place = (place+1)%conf.tsize + sp->tabno*conf.tsize;
+ sp->place = place;
+ } else
+ sp->seen = 1; /* now looking at current location */
+ DEBUG(("search @ %ld\n", place));
+
+ /* get the tagged value */
+ if (corepag != NULL && place < conf.tsize) {
+ DEBUG(("search: in core\n"));
+ value = MAPIN(corepag[place]);
+ } else {
+ /* seek, if necessary */
+ dest = place * SOF;
+ if (pagpos != dest) {
+ if (fseek(pagf, dest, SEEK_SET) != 0) {
+ DEBUG(("search: seek failed\n"));
+ pagpos = -1;
+ sp->aborted = 1;
+ return(NOTFOUND);
+ }
+ pagpos = dest;
+ }
+
+ /* read it */
+ if (fread((char *)&val, sizeof(val), 1, pagf) == 1)
+ value = MAPIN(val);
+ else if (ferror(pagf)) {
+ DEBUG(("search: read failed\n"));
+ pagpos = -1;
+ sp->aborted = 1;
+ return(NOTFOUND);
+ } else
+ value = VACANT;
+
+ /* and finish up */
+ pagpos += sizeof(val);
+ }
+
+ /* vacant slot is always cause to return */
+ if (value == VACANT) {
+ DEBUG(("search: empty slot\n"));
+ return(NOTFOUND);
+ };
+
+ /* check the tag */
+ value = UNBIAS(value);
+ DEBUG(("got 0x%lx\n", value));
+ if (!HASTAG(value)) {
+ DEBUG(("tagless\n"));
+ return(value);
+ } else if (TAG(value) == sp->tag) {
+ DEBUG(("match\n"));
+ return(NOTAG(value));
+ } else {
+ DEBUG(("mismatch 0x%lx\n", TAG(value)));
+ }
+ }
+ /* NOTREACHED */
+}
+
+/*
+ - okayvalue - check that a value can be stored
+ */
+static int /* predicate */
+okayvalue(value)
+of_t value;
+{
+ if (HASTAG(value))
+ return(0);
+#ifdef OVERFLOW
+ if (value == LONG_MAX) /* BIAS() and UNBIAS() will overflow */
+ return(0);
+#endif
+ return(1);
+}
+
+/*
+ - set - store a value into a location previously found by search
+ */
+static int /* 0 success, -1 failure */
+set(sp, value)
+register struct searcher *sp;
+of_t value;
+{
+ register of_t place = sp->place;
+ register of_t v = value;
+
+ if (sp->aborted)
+ return(-1);
+
+ if (CANTAG(v) && !conf.olddbz) {
+ v |= sp->tag | taghere;
+ if (v != UNBIAS(VACANT)) /* BIAS(v) won't look VACANT */
+#ifdef OVERFLOW
+ if (v != LONG_MAX) /* and it won't overflow */
+#endif
+ value = v;
+ }
+ DEBUG(("tagged value is 0x%lx\n", value));
+ value = BIAS(value);
+ value = MAPOUT(value);
+
+ /* If we have the index file in memory, use it */
+ if (corepag != NULL && place < conf.tsize) {
+ corepag[place] = value;
+ DEBUG(("set: incore\n"));
+ return(0);
+ }
+
+ /* seek to spot */
+ pagpos = -1; /* invalidate position memory */
+ if (fseek(pagf, place * SOF, SEEK_SET) != 0) {
+ DEBUG(("set: seek failed\n"));
+ sp->aborted = 1;
+ return(-1);
+ }
+
+ /* write in data */
+ if (fwrite((char *)&value, SOF, 1, pagf) != 1) {
+ DEBUG(("set: write failed\n"));
+ sp->aborted = 1;
+ return(-1);
+ }
+ /* fflush improves robustness, and buffer re-use is rare anyway */
+ if (fflush(pagf) == EOF) {
+ DEBUG(("set: fflush failed\n"));
+ sp->aborted = 1;
+ return(-1);
+ }
+
+ DEBUG(("set: succeeded\n"));
+ return(0);
+}
+
+/*
+ - mybytemap - determine this machine's byte map
+ *
+ * A byte map is an array of ints, sizeof(of_t) of them. The 0th int
+ * is the byte number of the high-order byte in my of_t, and so forth.
+ */
+static void
+mybytemap(map)
+int map[]; /* -> int[SOF] */
+{
+ union {
+ of_t o;
+ char c[SOF];
+ } u;
+ register int *mp = &map[SOF];
+ register int ntodo;
+ register int i;
+
+ u.o = 1;
+ for (ntodo = (int)SOF; ntodo > 0; ntodo--) {
+ for (i = 0; i < SOF; i++)
+ if (u.c[i] != 0)
+ break;
+ if (i == SOF) {
+ /* trouble -- set it to *something* consistent */
+ DEBUG(("mybytemap: nonexistent byte %d!!!\n", ntodo));
+ for (i = 0; i < SOF; i++)
+ map[i] = i;
+ return;
+ }
+ DEBUG(("mybytemap: byte %d\n", i));
+ *--mp = i;
+ while (u.c[i] != 0)
+ u.o <<= 1;
+ }
+}
+
+/*
+ - bytemap - transform an of_t from byte ordering map1 to map2
+ */
+static of_t /* transformed result */
+bytemap(ino, map1, map2)
+of_t ino;
+int *map1;
+int *map2;
+{
+ union oc {
+ of_t o;
+ char c[SOF];
+ };
+ union oc in;
+ union oc out;
+ register int i;
+
+ in.o = ino;
+ for (i = 0; i < SOF; i++)
+ out.c[map2[i]] = in.c[map1[i]];
+ return(out.o);
+}
+
+/*
+ * This is a simplified version of the pathalias hashing function.
+ * Thanks to Steve Belovin and Peter Honeyman
+ *
+ * hash a string into a long int. 31 bit crc (from andrew appel).
+ * the crc table is computed at run time by crcinit() -- we could
+ * precompute, but it takes 1 clock tick on a 750.
+ *
+ * This fast table calculation works only if POLY is a prime polynomial
+ * in the field of integers modulo 2. Since the coefficients of a
+ * 32-bit polynomial won't fit in a 32-bit word, the high-order bit is
+ * implicit. IT MUST ALSO BE THE CASE that the coefficients of orders
+ * 31 down to 25 are zero. Happily, we have candidates, from
+ * E. J. Watson, "Primitive Polynomials (Mod 2)", Math. Comp. 16 (1962):
+ * x^32 + x^7 + x^5 + x^3 + x^2 + x^1 + x^0
+ * x^31 + x^3 + x^0
+ *
+ * We reverse the bits to get:
+ * 111101010000000000000000000000001 but drop the last 1
+ * f 5 0 0 0 0 0 0
+ * 010010000000000000000000000000001 ditto, for 31-bit crc
+ * 4 8 0 0 0 0 0 0
+ */
+
+#define POLY 0x48000000L /* 31-bit polynomial (avoids sign problems) */
+
+static long CrcTable[128];
+
+/*
+ - crcinit - initialize tables for hash function
+ */
+static void
+crcinit()
+{
+ register int i, j;
+ register long sum;
+
+ for (i = 0; i < 128; ++i) {
+ sum = 0L;
+ for (j = 7 - 1; j >= 0; --j)
+ if (i & (1 << j))
+ sum ^= POLY >> j;
+ CrcTable[i] = sum;
+ }
+ DEBUG(("crcinit: done\n"));
+}
+
+/*
+ - hash - Honeyman's nice hashing function
+ */
+static long
+hash(name, size)
+register char *name;
+register int size;
+{
+ register long sum = 0L;
+
+ while (size--) {
+ sum = (sum >> 7) ^ CrcTable[(sum ^ (*name++)) & 0x7f];
+ }
+ DEBUG(("hash: returns (%ld)\n", sum));
+ return(sum);
+}
+
+/*
+ * case-mapping stuff
+ *
+ * Borrowed from C News, by permission of the authors. Somewhat modified.
+ *
+ * We exploit the fact that we are dealing only with headers here, and
+ * headers are limited to the ASCII characters by RFC822. It is barely
+ * possible that we might be dealing with a translation into another
+ * character set, but in particular it's very unlikely for a header
+ * character to be outside -128..255.
+ *
+ * Life would be a whole lot simpler if tolower() could safely and portably
+ * be applied to any char.
+ */
+
+#define OFFSET 128 /* avoid trouble with negative chars */
+
+/* must call casencmp before invoking TOLOW... */
+#define TOLOW(c) (cmap[(c)+OFFSET])
+
+/* ...but the use of it in CISTREQN is safe without the preliminary call (!) */
+/* CISTREQN is an optimised case-insensitive strncmp(a,b,n)==0; n > 0 */
+#define CISTREQN(a, b, n) \
+ (TOLOW((a)[0]) == TOLOW((b)[0]) && casencmp(a, b, n) == 0)
+
+#define MAPSIZE (256+OFFSET)
+static char cmap[MAPSIZE]; /* relies on init to '\0' */
+static int mprimed = 0; /* has cmap been set up? */
+
+/*
+ - mapprime - set up case-mapping stuff
+ */
+static void
+mapprime()
+{
+ register char *lp;
+ register char *up;
+ register int c;
+ register int i;
+ static char lower[] = "abcdefghijklmnopqrstuvwxyz";
+ static char upper[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
+
+ for (lp = lower, up = upper; *lp != '\0'; lp++, up++) {
+ c = *lp;
+ cmap[c+OFFSET] = c;
+ cmap[*up+OFFSET] = c;
+ }
+ for (i = 0; i < MAPSIZE; i++)
+ if (cmap[i] == '\0')
+ cmap[i] = (char)(i-OFFSET);
+ mprimed = 1;
+}
+
+/*
+ - casencmp - case-independent strncmp
+ */
+static int /* < == > 0 */
+casencmp(s1, s2, len)
+char *s1;
+char *s2;
+int len;
+{
+ register char *p1;
+ register char *p2;
+ register int n;
+
+ if (!mprimed)
+ mapprime();
+
+ p1 = s1;
+ p2 = s2;
+ n = len;
+ while (--n >= 0 && *p1 != '\0' && TOLOW(*p1) == TOLOW(*p2)) {
+ p1++;
+ p2++;
+ }
+ if (n < 0)
+ return(0);
+
+ /*
+ * The following case analysis is necessary so that characters
+ * which look negative collate low against normal characters but
+ * high against the end-of-string NUL.
+ */
+ if (*p1 == '\0' && *p2 == '\0')
+ return(0);
+ else if (*p1 == '\0')
+ return(-1);
+ else if (*p2 == '\0')
+ return(1);
+ else
+ return(TOLOW(*p1) - TOLOW(*p2));
+}
+
+/*
+ - mapcase - do case-mapped copy
+ */
+static char * /* returns src or dst */
+mapcase(dst, src, siz)
+char *dst; /* destination, used only if mapping needed */
+char *src; /* source; src == dst is legal */
+size_t siz;
+{
+ register char *s;
+ register char *d;
+ register char *c; /* case break */
+ register char *e; /* end of source */
+
+
+ c = cipoint(src, siz);
+ if (c == NULL)
+ return(src);
+
+ if (!mprimed)
+ mapprime();
+ s = src;
+ e = s + siz;
+ d = dst;
+
+ while (s < c)
+ *d++ = *s++;
+ while (s < e)
+ *d++ = TOLOW(*s++);
+
+ return(dst);
+}
+
+/*
+ - cipoint - where in this message-ID does it become case-insensitive?
+ *
+ * The RFC822 code is not quite complete. Absolute, total, full RFC822
+ * compliance requires a horrible parsing job, because of the arcane
+ * quoting conventions -- abc"def"ghi is not equivalent to abc"DEF"ghi,
+ * for example. There are three or four things that might occur in the
+ * domain part of a message-id that are case-sensitive. They don't seem
+ * to ever occur in real news, thank Cthulhu. (What? You were expecting
+ * a merciful and forgiving deity to be invoked in connection with RFC822?
+ * Forget it; none of them would come near it.)
+ */
+static char * /* pointer into s, or NULL for "nowhere" */
+cipoint(s, siz)
+char *s;
+size_t siz;
+{
+ register char *p;
+ static char post[] = "postmaster";
+ static int plen = sizeof(post)-1;
+
+ switch (conf.casemap) {
+ case '0': /* unmapped, sensible */
+ return(NULL);
+ break;
+ case 'C': /* C News, RFC 822 conformant (approx.) */
+ p = memchr(s, '@', siz);
+ if (p == NULL) /* no local/domain split */
+ return(NULL); /* assume all local */
+ else if (p - (s+1) == plen && CISTREQN(s+1, post, plen)) {
+ /* crazy -- "postmaster" is case-insensitive */
+ return(s);
+ } else
+ return(p);
+ break;
+ case '=': /* 2.11, neither sensible nor conformant */
+ return(s); /* all case-insensitive */
+ break;
+ }
+
+ DEBUG(("cipoint: unknown case mapping `%c'\n", conf.casemap));
+ return(NULL); /* just leave it alone */
+}
+
+/*
+ - dbzdebug - control dbz debugging at run time
+ */
+int /* old value */
+dbzdebug(value)
+int value;
+{
+#ifdef DBZDEBUG
+ register int old = debug;
+
+ debug = value;
+ return(old);
+#else
+ return(-1);
+#endif
+}
diff --git a/news/nntpbtr/files/dbz/dbz.h b/news/nntpbtr/files/dbz/dbz.h
new file mode 100644
index 000000000000..3d7e8ed702cb
--- /dev/null
+++ b/news/nntpbtr/files/dbz/dbz.h
@@ -0,0 +1,32 @@
+/* for dbm and dbz */
+typedef struct {
+ char *dptr;
+ int dsize;
+} datum;
+
+/* standard dbm functions */
+extern int dbminit();
+extern datum fetch();
+extern int store();
+extern int delete(); /* not in dbz */
+extern datum firstkey(); /* not in dbz */
+extern datum nextkey(); /* not in dbz */
+extern int dbmclose(); /* in dbz, but not in old dbm */
+
+/* new stuff for dbz */
+extern int dbzfresh();
+extern int dbzagain();
+extern datum dbzfetch();
+extern int dbzstore();
+extern int dbzsync();
+extern long dbzsize();
+extern int dbzincore();
+extern int dbzcancel();
+extern int dbzdebug();
+
+/*
+ * In principle we could handle unlimited-length keys by operating a chunk
+ * at a time, but it's not worth it in practice. Setting a nice large
+ * bound on them simplifies the code and doesn't hurt anything.
+ */
+#define DBZMAXKEY 255
diff --git a/news/nntpbtr/files/patch-aa b/news/nntpbtr/files/patch-aa
new file mode 100644
index 000000000000..d8ef101948c0
--- /dev/null
+++ b/news/nntpbtr/files/patch-aa
@@ -0,0 +1,30 @@
+--- btrspc.c 1995/03/05 15:32:21 1.3
++++ btrspc.c 1996/07/02 05:26:51
+@@ -98,7 +98,10 @@
+ #define blkavail(fs) ((int)((fs).fd_req.bfreen))
+ #define filavail(fs) ((int)((fs).fd_req.gfree))
+
+-#elif defined(__bsdi__)
++#elif defined(__bsdi__) || defined(__FreeBSD__)
++#if defined(__FreeBSD__)
++#include <sys/param.h>
++#endif
+ #include <sys/mount.h>
+ typedef struct statfs statfs_type;
+ #define statfilesys statfs
+--- nntpbtr.c 1995/06/11 13:13:12 1.7
++++ nntpbtr.c 1996/07/02 06:05:51
+@@ -49,11 +49,12 @@
+ struct passwd *getpwnam();
+ #endif
+
+-off_t bytecount = 0;
+ off_t bfdpos;
+ char batchtmp[] = BATCHTMP;
+
+ #endif
++
++off_t bytecount = 0;
+
+ long MINfree = MINFREE;
+ int MAXINfiles = MAXINFILES;
diff --git a/news/nntpbtr/files/patch-ab b/news/nntpbtr/files/patch-ab
new file mode 100644
index 000000000000..6e833db604f0
--- /dev/null
+++ b/news/nntpbtr/files/patch-ab
@@ -0,0 +1,13 @@
+--- Makefile 1994/11/25 15:09:23 1.1
++++ Makefile 1996/07/02 05:37:31
+@@ -32,3 +32,10 @@
+
+ clean:
+ rm -f *.o nntpbtr
++
++all: nntpbtr
++
++install:
++ install -c -m555 nntpbtr ${PREFIX}/sbin
++ install -c -m555 nntpbtr.1 ${PREFIX}/man/man1
++
diff --git a/news/nntpbtr/files/patch-ac b/news/nntpbtr/files/patch-ac
new file mode 100644
index 000000000000..7fc2de86e97a
--- /dev/null
+++ b/news/nntpbtr/files/patch-ac
@@ -0,0 +1,52 @@
+--- conf.h 1995/06/11 13:16:09 1.6
++++ conf.h 1996/07/02 06:08:08
+@@ -4,16 +4,16 @@
+ */
+
+ /* It is defined for direct pipe to INND without disk batches */
+-/* #define RNEWS "/usr/bin/rnews" */
++#define RNEWS "/usr/local/bin/rnews"
+
+ /* if undef DBZ and NDBM, then history check will not use */
+ /* please use the definition from your CNEWS/INN distributive */
+-#define HISTORY_FILE "/usr/lib/news/history"
++#define HISTORY_FILE "/usr/local/news/lib/history"
+ #define DBZ
+ #undef NDBM
+
+ /* the position of host data file - control info for nntpbtr */
+-#define HOSTDIR "/usr/spool/news/nntpbtr-"
++#define HOSTDIR "/var/spool/news/nntpbtr-"
+
+ #ifndef RNEWS
+ /* This is the batch file definitions - where, size and cmd to input news
+@@ -24,22 +24,24 @@
+ #define BATCHINPUTCMD "/usr/lib/newsbin/input/newsrun", "newsrun", 0
+ #endif
+ #define BATCHSIZE 300000
+-#define BATCHTMP "/usr/spool/news/in.coming/nntp.XXXXXX"
+-#define BATCHDIR "/usr/spool/news/in.coming/"
++#define BATCHTMP "/var/spool/news/in.coming/nntp.XXXXXX"
++#define BATCHDIR "/var/spool/news/in.coming/"
+ #endif
+
+ /* This is defined the only for the purpose of disk free check.
+ min free space is in the BLOCKS (1KB or 512 - depend from system).
+ dont't forget here about free space for expire process in CNEWS -
+ - it is need the second "history" file space */
+-#define NEWSSPOOL "/usr/spool/news"
++#define NEWSSPOOL "/var/spool/news"
+ #define MINFREE 30000
+ #define MINFILES 2048 /* one 300KB batch = 150 files, 10 batches enough ? */
++
+ #ifndef RNEWS
+-#define NEWSINCOMING "/usr/spool/news/in.coming"
++#define NEWSINCOMING "/var/spool/news/in.coming"
+ /* max inputed files in BATCHDIR (-I) */
+-#define MAXINFILES 0 /* max # files in BATCHDIR; 0 -> no check */
+ #endif
++#define MAXINFILES 0 /* max # files in BATCHDIR; 0 -> no check */
++
+ /* wait time slot to re-check disk space (-m) or batch process (-I)
+ if not define, when exit(2) without wait */
+ /* #define MINFTIME 60*10 /* time to sleep between new check */
diff --git a/news/nntpbtr/pkg-comment b/news/nntpbtr/pkg-comment
new file mode 100644
index 000000000000..feb9293d4319
--- /dev/null
+++ b/news/nntpbtr/pkg-comment
@@ -0,0 +1 @@
+NNTP bulk transfer
diff --git a/news/nntpbtr/pkg-descr b/news/nntpbtr/pkg-descr
new file mode 100644
index 000000000000..0f0a9b34cdd4
--- /dev/null
+++ b/news/nntpbtr/pkg-descr
@@ -0,0 +1,2 @@
+nntpbtr is high-throughput, well-buffered, crash-reliable, all-terrains
+NNTP Bulk TRanfer program, written by Leonid Yegoshin <egoshin@ihep.su>, LY22.
diff --git a/news/nntpbtr/pkg-plist b/news/nntpbtr/pkg-plist
new file mode 100644
index 000000000000..270e376e2694
--- /dev/null
+++ b/news/nntpbtr/pkg-plist
@@ -0,0 +1,2 @@
+sbin/nntpbtr
+man/man1/nntpbtr.1.gz