chiark / gitweb /
REORG Delete everything that's not innduct or build system or changed for innduct
[inn-innduct.git] / storage / tradspool / tradspool.c
diff --git a/storage/tradspool/tradspool.c b/storage/tradspool/tradspool.c
deleted file mode 100644 (file)
index 0df3598..0000000
+++ /dev/null
@@ -1,1313 +0,0 @@
-/*  $Id: tradspool.c 7412 2005-10-09 03:44:35Z eagle $
-**
-**  Storage manager module for traditional spool format.
-*/
-
-#include "config.h"
-#include "clibrary.h"
-#include "portable/mmap.h"
-#include <ctype.h>
-#include <dirent.h>
-#include <errno.h>
-#include <fcntl.h>
-#include <syslog.h>
-#include <sys/stat.h>
-#include <sys/uio.h>
-#include <time.h>
-
-/* Needed for htonl() and friends on AIX 4.1. */
-#include <netinet/in.h>
-    
-#include "inn/innconf.h"
-#include "inn/qio.h"
-#include "inn/wire.h"
-#include "libinn.h"
-#include "paths.h"
-
-#include "methods.h"
-#include "tradspool.h"
-
-typedef struct {
-    char               *artbase; /* start of the article data -- may be mmaped */
-    unsigned int       artlen; /* art length. */
-    int        nextindex;
-    char               *curdirname;
-    DIR                        *curdir;
-    struct _ngtent     *ngtp;
-    bool               mmapped;
-} PRIV_TRADSPOOL;
-
-/*
-** The 64-bit hashed representation of a ng name that gets stashed in each token. 
-*/
-
-#define HASHEDNGLEN 8
-typedef struct {
-    char hash[HASHEDNGLEN];
-} HASHEDNG;
-
-/*
-** We have two structures here for facilitating newsgroup name->number mapping
-** and number->name mapping.  NGTable is a hash table based on hashing the
-** newsgroup name, and is used to give the name->number mapping.  NGTree is
-** a binary tree, indexed by newsgroup number, used for the number->name
-** mapping.
-*/
-
-#define NGT_SIZE  2048
-
-typedef struct _ngtent {
-    char *ngname;
-/*    HASHEDNG hash; XXX */
-    unsigned long ngnumber;
-    struct _ngtent *next;
-    struct _ngtreenode *node;
-} NGTENT;
-
-typedef struct _ngtreenode {
-    unsigned long ngnumber;
-    struct _ngtreenode *left, *right;
-    NGTENT *ngtp;
-} NGTREENODE;
-
-NGTENT *NGTable[NGT_SIZE];
-unsigned long MaxNgNumber = 0;
-NGTREENODE *NGTree;
-
-bool NGTableUpdated; /* set to true if we've added any entries since reading 
-                       in the database file */
-
-/* 
-** Convert all .s to /s in a newsgroup name.  Modifies the passed string 
-** inplace.
-*/
-static void
-DeDotify(char *ngname) {
-    char *p = ngname;
-
-    for ( ; *p ; ++p) {
-       if (*p == '.') *p = '/';
-    }
-    return;
-}
-
-/*
-** Hash a newsgroup name to an 8-byte.  Basically, we convert all .s to 
-** /s (so it doesn't matter if we're passed the spooldir name or newsgroup
-** name) and then call Hash to MD5 the mess, then take 4 bytes worth of 
-** data from the front of the hash.  This should be good enough for our
-** purposes. 
-*/
-
-static HASHEDNG 
-HashNGName(char *ng) {
-    HASH hash;
-    HASHEDNG return_hash;
-    char *p;
-
-    p = xstrdup(ng);
-    DeDotify(p);
-    hash = Hash(p, strlen(p));
-    free(p);
-
-    memcpy(return_hash.hash, hash.hash, HASHEDNGLEN);
-
-    return return_hash;
-}
-
-#if 0 /* XXX */
-/* compare two hashes */
-static int
-CompareHash(HASHEDNG *h1, HASHEDNG *h2) {
-    int i;
-    for (i = 0 ; i < HASHEDNGLEN ; ++i) {
-       if (h1->hash[i] != h2->hash[i]) {
-           return h1->hash[i] - h2->hash[i];
-       }
-    }
-    return 0;
-}
-#endif
-
-/* Add a new newsgroup name to the NG table. */
-static void
-AddNG(char *ng, unsigned long number) {
-    char *p;
-    unsigned int h;
-    HASHEDNG hash;
-    NGTENT *ngtp, **ngtpp;
-    NGTREENODE *newnode, *curnode, **nextnode;
-
-    p = xstrdup(ng);
-    DeDotify(p); /* canonicalize p to standard (/) form. */
-    hash = HashNGName(p);
-
-    h = (unsigned char)hash.hash[0];
-    h = h + (((unsigned char)hash.hash[1])<<8);
-
-    h = h % NGT_SIZE;
-
-    ngtp = NGTable[h];
-    ngtpp = &NGTable[h];
-    while (true) {
-       if (ngtp == NULL) {
-           /* ng wasn't in table, add new entry. */
-           NGTableUpdated = true;
-
-           ngtp = xmalloc(sizeof(NGTENT));
-           ngtp->ngname = p; /* note: we store canonicalized name */
-           /* ngtp->hash = hash XXX */
-           ngtp->next = NULL;
-
-           /* assign a new NG number if needed (not given) */
-           if (number == 0) {
-               number = ++MaxNgNumber;
-           }
-           ngtp->ngnumber = number;
-
-           /* link new table entry into the hash table chain. */
-           *ngtpp = ngtp;
-
-           /* Now insert an appropriate record into the binary tree */
-           newnode = xmalloc(sizeof(NGTREENODE));
-           newnode->left = newnode->right = (NGTREENODE *) NULL;
-           newnode->ngnumber = number;
-           newnode->ngtp = ngtp;
-           ngtp->node = newnode;
-
-           if (NGTree == NULL) {
-               /* tree was empty, so put our one element in and return */
-               NGTree = newnode;
-               return;
-           } else {
-               nextnode = &NGTree;
-               while (*nextnode) {
-                   curnode = *nextnode;
-                   if (curnode->ngnumber < number) {
-                       nextnode = &curnode->right;
-                   } else if (curnode->ngnumber > number) {
-                       nextnode = &curnode->left;
-                   } else {
-                       /* Error, same number is already in NGtree (shouldn't happen!) */
-                       syslog(L_ERROR, "tradspool: AddNG: duplicate newsgroup number in NGtree: %ld(%s)", number, p);
-                       return;
-                   }
-               }
-               *nextnode = newnode;
-               return;
-           }
-       } else if (strcmp(ngtp->ngname, p) == 0) {
-           /* entry in table already, so return */
-           free(p);
-           return;
-#if 0 /* XXX */
-       } else if (CompareHash(&ngtp->hash, &hash) == 0) {
-           /* eep! we hit a hash collision. */
-           syslog(L_ERROR, "tradspool: AddNG: Hash collison %s/%s", ngtp->ngname, p);
-           free(p);
-           return;
-#endif 
-       } else {
-           /* not found yet, so advance to next entry in chain */
-           ngtpp = &(ngtp->next);
-           ngtp = ngtp->next;
-       }
-    }
-}
-
-/* find a newsgroup table entry, given only the name. */
-static NGTENT *
-FindNGByName(char *ngname) {
-    NGTENT *ngtp;
-    unsigned int h;
-    HASHEDNG hash;
-    char *p;
-
-    p = xstrdup(ngname);
-    DeDotify(p); /* canonicalize p to standard (/) form. */
-    hash = HashNGName(p);
-
-    h = (unsigned char)hash.hash[0];
-    h = h + (((unsigned char)hash.hash[1])<<8);
-
-    h = h % NGT_SIZE;
-
-    ngtp = NGTable[h];
-
-    while (ngtp) {
-       if (strcmp(p, ngtp->ngname) == 0) {
-           free(p);
-           return ngtp;
-       }
-       ngtp = ngtp->next;
-    }
-    free(p);
-    return NULL; 
-}
-
-/* find a newsgroup/spooldir name, given only the newsgroup number */
-static char *
-FindNGByNum(unsigned long ngnumber) {
-    NGTENT *ngtp;
-    NGTREENODE *curnode;
-
-    curnode = NGTree;
-
-    while (curnode) {
-       if (curnode->ngnumber == ngnumber) {
-           ngtp = curnode->ngtp;
-           return ngtp->ngname;
-       }
-       if (curnode->ngnumber < ngnumber) {
-           curnode = curnode->right;
-       } else {
-           curnode = curnode->left;
-       }
-    }
-    /* not in tree, return NULL */
-    return NULL; 
-}
-
-#define _PATH_TRADSPOOLNGDB "tradspool.map"
-#define _PATH_NEWTSNGDB "tradspool.map.new"
-
-
-/* dump DB to file. */
-static void
-DumpDB(void)
-{
-    char *fname, *fnamenew;
-    NGTENT *ngtp;
-    unsigned int i;
-    FILE *out;
-
-    if (!SMopenmode) return; /* don't write if we're not in read/write mode. */
-    if (!NGTableUpdated) return; /* no need to dump new DB */
-
-    fname = concatpath(innconf->pathspool, _PATH_TRADSPOOLNGDB);
-    fnamenew = concatpath(innconf->pathspool, _PATH_NEWTSNGDB);
-
-    if ((out = fopen(fnamenew, "w")) == NULL) {
-       syslog(L_ERROR, "tradspool: DumpDB: can't write %s: %m", fnamenew);
-       free(fname);
-       free(fnamenew);
-       return;
-    }
-    for (i = 0 ; i < NGT_SIZE ; ++i) {
-       ngtp = NGTable[i];
-       for ( ; ngtp ; ngtp = ngtp->next) {
-           fprintf(out, "%s %lu\n", ngtp->ngname, ngtp->ngnumber);
-       }
-    }
-    if (fclose(out) < 0) {
-       syslog(L_ERROR, "tradspool: DumpDB: can't close %s: %m", fnamenew);
-       free(fname);
-       free(fnamenew);
-       return;
-    }
-    if (rename(fnamenew, fname) < 0) {
-       syslog(L_ERROR, "tradspool: can't rename %s", fnamenew);
-       free(fname);
-       free(fnamenew);
-       return;
-    }
-    free(fname);
-    free(fnamenew);
-    NGTableUpdated = false; /* reset modification flag. */
-    return;
-}
-
-/* 
-** init NGTable from saved database file and from active.  Note that
-** entries in the database file get added first,  and get their specifications
-** of newsgroup number from there. 
-*/
-
-static bool
-ReadDBFile(void)
-{
-    char *fname;
-    QIOSTATE *qp;
-    char *line;
-    char *p;
-    unsigned long number;
-
-    fname = concatpath(innconf->pathspool, _PATH_TRADSPOOLNGDB);
-    if ((qp = QIOopen(fname)) == NULL) {
-       /* only warn if db not found. */
-       syslog(L_NOTICE, "tradspool: %s not found", fname);
-    } else {
-       while ((line = QIOread(qp)) != NULL) {
-           p = strchr(line, ' ');
-           if (p == NULL) {
-               syslog(L_FATAL, "tradspool: corrupt line in active %s", line);
-               QIOclose(qp);
-               free(fname);
-               return false;
-           }
-           *p++ = 0;
-           number = atol(p);
-           AddNG(line, number);
-           if (MaxNgNumber < number) MaxNgNumber = number;
-       }
-       QIOclose(qp);
-    }
-    free(fname);
-    return true;
-}
-
-static bool
-ReadActiveFile(void)
-{
-    char *fname;
-    QIOSTATE *qp;
-    char *line;
-    char *p;
-
-    fname = concatpath(innconf->pathdb, _PATH_ACTIVE);
-    if ((qp = QIOopen(fname)) == NULL) {
-       syslog(L_FATAL, "tradspool: can't open %s", fname);
-       free(fname);
-       return false;
-    }
-
-    while ((line = QIOread(qp)) != NULL) {
-       p = strchr(line, ' ');
-       if (p == NULL) {
-           syslog(L_FATAL, "tradspool: corrupt line in active %s", line);
-           QIOclose(qp);
-           free(fname);
-           return false;
-       }
-       *p = 0;
-       AddNG(line, 0);
-    }
-    QIOclose(qp);
-    free(fname);
-    /* dump any newly added changes to database */
-    DumpDB();
-    return true;
-}
-
-static bool
-InitNGTable(void)
-{
-    if (!ReadDBFile()) return false;
-
-    /*
-    ** set NGTableUpdated to false; that way we know if the load of active or
-    ** any AddNGs later on did in fact add new entries to the db.
-    */
-    NGTableUpdated = false; 
-    if (!SMopenmode)
-       /* don't read active unless write mode. */
-       return true;
-    return ReadActiveFile(); 
-}
-
-/* 
-** Routine called to check every so often to see if we need to reload the
-** database and add in any new groups that have been added.   This is primarily
-** for the benefit of innfeed in funnel mode, which otherwise would never
-** get word that any new newsgroups had been added. 
-*/
-
-#define RELOAD_TIME_CHECK 600
-
-static void
-CheckNeedReloadDB(bool force)
-{
-    static TIMEINFO lastcheck, oldlastcheck, now;
-    struct stat sb;
-    char *fname;
-
-    if (GetTimeInfo(&now) < 0) return; /* anyone ever seen gettimeofday fail? :-) */
-    if (!force && lastcheck.time + RELOAD_TIME_CHECK > now.time) return;
-
-    oldlastcheck = lastcheck;
-    lastcheck = now;
-
-    fname = concatpath(innconf->pathspool, _PATH_TRADSPOOLNGDB);
-    if (stat(fname, &sb) < 0) {
-       free(fname);
-       return;
-    }
-    free(fname);
-    if (sb.st_mtime > oldlastcheck.time) {
-       /* add any newly added ngs to our in-memory copy of the db. */
-       ReadDBFile();
-    }
-}
-
-/* Init routine, called by SMinit */
-
-bool
-tradspool_init(SMATTRIBUTE *attr) {
-    if (attr == NULL) {
-       syslog(L_ERROR, "tradspool: attr is NULL");
-       SMseterror(SMERR_INTERNAL, "attr is NULL");
-       return false;
-    }
-    if (!innconf->storeonxref) {
-       syslog(L_ERROR, "tradspool: storeonxref needs to be true");
-       SMseterror(SMERR_INTERNAL, "storeonxref needs to be true");
-       return false;
-    }
-    attr->selfexpire = false;
-    attr->expensivestat = true;
-    return InitNGTable();
-}
-
-/* Make a token for an article given the primary newsgroup name and article # */
-static TOKEN
-MakeToken(char *ng, unsigned long artnum, STORAGECLASS class) {
-    TOKEN token;
-    NGTENT *ngtp;
-    unsigned long num;
-
-    memset(&token, '\0', sizeof(token));
-
-    token.type = TOKEN_TRADSPOOL;
-    token.class = class;
-
-    /* 
-    ** if not already in the NG Table, be sure to add this ng! This way we
-    ** catch things like newsgroups added since startup. 
-    */
-    if ((ngtp = FindNGByName(ng)) == NULL) {
-       AddNG(ng, 0);
-       DumpDB(); /* flush to disk so other programs can see the change */
-       ngtp = FindNGByName(ng);
-    } 
-
-    num = ngtp->ngnumber;
-    num = htonl(num);
-
-    memcpy(token.token, &num, sizeof(num));
-    artnum = htonl(artnum);
-    memcpy(&token.token[sizeof(num)], &artnum, sizeof(artnum));
-    return token;
-}
-
-/* 
-** Convert a token back to a pathname. 
-*/
-static char *
-TokenToPath(TOKEN token) {
-    unsigned long ngnum;
-    unsigned long artnum;
-    char *ng, *path;
-    size_t length;
-
-    CheckNeedReloadDB(false);
-
-    memcpy(&ngnum, &token.token[0], sizeof(ngnum));
-    memcpy(&artnum, &token.token[sizeof(ngnum)], sizeof(artnum));
-    artnum = ntohl(artnum);
-    ngnum = ntohl(ngnum);
-
-    ng = FindNGByNum(ngnum);
-    if (ng == NULL) {
-       CheckNeedReloadDB(true);
-       ng = FindNGByNum(ngnum);
-       if (ng == NULL)
-           return NULL;
-    }
-
-    length = strlen(ng) + 20 + strlen(innconf->patharticles);
-    path = xmalloc(length);
-    snprintf(path, length, "%s/%s/%lu", innconf->patharticles, ng, artnum);
-    return path;
-}
-
-/*
-** Crack an Xref line apart into separate strings, each of the form "ng:artnum".
-** Return in "num" the number of newsgroups found. 
-*/
-static char **
-CrackXref(char *xref, unsigned int *lenp) {
-    char *p;
-    char **xrefs;
-    char *q;
-    unsigned int len, xrefsize;
-
-    len = 0;
-    xrefsize = 5;
-    xrefs = xmalloc(xrefsize * sizeof(char *));
-
-    /* no path element should exist, nor heading white spaces exist */
-    p = xref;
-    while (true) {
-       /* check for EOL */
-       /* shouldn't ever hit null w/o hitting a \r\n first, but best to be paranoid */
-       if (*p == '\n' || *p == '\r' || *p == 0) {
-           /* hit EOL, return. */
-           *lenp = len;
-           return xrefs;
-       }
-       /* skip to next space or EOL */
-       for (q=p; *q && *q != ' ' && *q != '\n' && *q != '\r' ; ++q) ;
-
-        xrefs[len] = xstrndup(p, q - p);
-
-       if (++len == xrefsize) {
-           /* grow xrefs if needed. */
-           xrefsize *= 2;
-            xrefs = xrealloc(xrefs, xrefsize * sizeof(char *));
-       }
-
-       p = q;
-       /* skip spaces */
-       for ( ; *p == ' ' ; p++) ;
-    }
-}
-
-TOKEN
-tradspool_store(const ARTHANDLE article, const STORAGECLASS class) {
-    char **xrefs;
-    char *xrefhdr;
-    TOKEN token;
-    unsigned int numxrefs;
-    char *ng, *p, *onebuffer;
-    unsigned long artnum;
-    char *path, *linkpath, *dirname;
-    int fd;
-    size_t used;
-    char *nonwfarticle; /* copy of article converted to non-wire format */
-    unsigned int i;
-    size_t length, nonwflen;
-    
-    xrefhdr = article.groups;
-    if ((xrefs = CrackXref(xrefhdr, &numxrefs)) == NULL || numxrefs == 0) {
-       token.type = TOKEN_EMPTY;
-       SMseterror(SMERR_UNDEFINED, "bogus Xref: header");
-       if (xrefs != NULL)
-           free(xrefs);
-       return token;
-    }
-
-    if ((p = strchr(xrefs[0], ':')) == NULL) {
-       token.type = TOKEN_EMPTY;
-       SMseterror(SMERR_UNDEFINED, "bogus Xref: header");
-       for (i = 0 ; i < numxrefs; ++i) free(xrefs[i]);
-       free(xrefs);
-       return token;
-    }
-    *p++ = '\0';
-    ng = xrefs[0];
-    DeDotify(ng);
-    artnum = atol(p);
-    
-    token = MakeToken(ng, artnum, class);
-
-    length = strlen(innconf->patharticles) + strlen(ng) + 32;
-    path = xmalloc(length);
-    snprintf(path, length, "%s/%s/%lu", innconf->patharticles, ng, artnum);
-
-    /* following chunk of code boldly stolen from timehash.c  :-) */
-    if ((fd = open(path, O_CREAT|O_EXCL|O_WRONLY, ARTFILE_MODE)) < 0) {
-       p = strrchr(path, '/');
-       *p = '\0';
-       if (!MakeDirectory(path, true)) {
-           syslog(L_ERROR, "tradspool: could not make directory %s %m", path);
-           token.type = TOKEN_EMPTY;
-           free(path);
-           SMseterror(SMERR_UNDEFINED, NULL);
-           for (i = 0 ; i < numxrefs; ++i) free(xrefs[i]);
-           free(xrefs);
-           return token;
-       } else {
-           *p = '/';
-           if ((fd = open(path, O_CREAT|O_EXCL|O_WRONLY, ARTFILE_MODE)) < 0) {
-               SMseterror(SMERR_UNDEFINED, NULL);
-               syslog(L_ERROR, "tradspool: could not open %s %m", path);
-               token.type = TOKEN_EMPTY;
-               free(path);
-               for (i = 0 ; i < numxrefs; ++i) free(xrefs[i]);
-               free(xrefs);
-               return token;
-           }
-       }
-    }
-    if (innconf->wireformat) {
-       if (xwritev(fd, article.iov, article.iovcnt) != (ssize_t) article.len) {
-           SMseterror(SMERR_UNDEFINED, NULL);
-           syslog(L_ERROR, "tradspool error writing %s %m", path);
-           close(fd);
-           token.type = TOKEN_EMPTY;
-           unlink(path);
-           free(path);
-           for (i = 0 ; i < numxrefs; ++i) free(xrefs[i]);
-           free(xrefs);
-           return token;
-       }
-    } else {
-       onebuffer = xmalloc(article.len);
-       for (used = i = 0 ; i < article.iovcnt ; i++) {
-           memcpy(&onebuffer[used], article.iov[i].iov_base, article.iov[i].iov_len);
-           used += article.iov[i].iov_len;
-       }
-       nonwfarticle = FromWireFmt(onebuffer, used, &nonwflen);
-       free(onebuffer);
-       if (write(fd, nonwfarticle, nonwflen) != (ssize_t) nonwflen) {
-           free(nonwfarticle);
-           SMseterror(SMERR_UNDEFINED, NULL);
-           syslog(L_ERROR, "tradspool error writing %s %m", path);
-           close(fd);
-           token.type = TOKEN_EMPTY;
-           unlink(path);
-           free(path);
-           for (i = 0 ; i < numxrefs; ++i) free(xrefs[i]);
-           free(xrefs);
-           return token;
-       }
-       free(nonwfarticle);
-    }
-    close(fd);
-
-    /* 
-    ** blah, this is ugly.  Have to make symlinks under other pathnames for
-    ** backwards compatiblility purposes. 
-    */
-
-    if (numxrefs > 1) {
-       for (i = 1; i < numxrefs ; ++i) {
-           if ((p = strchr(xrefs[i], ':')) == NULL) continue;
-           *p++ = '\0';
-           ng = xrefs[i];
-           DeDotify(ng);
-           artnum = atol(p);
-
-            length = strlen(innconf->patharticles) + strlen(ng) + 32;
-           linkpath = xmalloc(length);
-           snprintf(linkpath, length, "%s/%s/%lu", innconf->patharticles,
-                     ng, artnum);
-           if (link(path, linkpath) < 0) {
-               p = strrchr(linkpath, '/');
-               *p = '\0';
-               dirname = xstrdup(linkpath);
-               *p = '/';
-               if (!MakeDirectory(dirname, true) || link(path, linkpath) < 0) {
-#if !defined(HAVE_SYMLINK)
-                   SMseterror(SMERR_UNDEFINED, NULL);
-                   syslog(L_ERROR, "tradspool: could not link %s to %s %m", path, linkpath);
-                   token.type = TOKEN_EMPTY;
-                   free(dirname);
-                   free(linkpath);
-                   free(path);
-                   for (i = 0 ; i < numxrefs; ++i) free(xrefs[i]);
-                   free(xrefs);
-                   return token;
-#else
-                   if (symlink(path, linkpath) < 0) {
-                       SMseterror(SMERR_UNDEFINED, NULL);
-                       syslog(L_ERROR, "tradspool: could not symlink %s to %s %m", path, linkpath);
-                       token.type = TOKEN_EMPTY;
-                       free(dirname);
-                       free(linkpath);
-                       free(path);
-                       for (i = 0 ; i < numxrefs; ++i) free(xrefs[i]);
-                       free(xrefs);
-                       return token;
-                   }
-#endif  /* !defined(HAVE_SYMLINK) */
-               }
-               free(dirname);
-           }
-           free(linkpath);
-       }
-    }
-    free(path);
-    for (i = 0 ; i < numxrefs; ++i) free(xrefs[i]);
-    free(xrefs);
-    return token;
-}
-
-static ARTHANDLE *
-OpenArticle(const char *path, RETRTYPE amount) {
-    int fd;
-    PRIV_TRADSPOOL *private;
-    char *p;
-    struct stat sb;
-    ARTHANDLE *art;
-    char *wfarticle;
-    size_t wflen;
-
-    if (amount == RETR_STAT) {
-       if (access(path, R_OK) < 0) {
-           SMseterror(SMERR_UNDEFINED, NULL);
-           return NULL;
-       }
-       art = xmalloc(sizeof(ARTHANDLE));
-       art->type = TOKEN_TRADSPOOL;
-       art->data = NULL;
-       art->len = 0;
-       art->private = NULL;
-       return art;
-    }
-
-    if ((fd = open(path, O_RDONLY)) < 0) {
-       SMseterror(SMERR_UNDEFINED, NULL);
-       return NULL;
-    }
-
-    art = xmalloc(sizeof(ARTHANDLE));
-    art->type = TOKEN_TRADSPOOL;
-
-    if (fstat(fd, &sb) < 0) {
-       SMseterror(SMERR_UNDEFINED, NULL);
-       syslog(L_ERROR, "tradspool: could not fstat article: %m");
-       free(art);
-       close(fd);
-       return NULL;
-    }
-
-    art->arrived = sb.st_mtime;
-
-    private = xmalloc(sizeof(PRIV_TRADSPOOL));
-    art->private = (void *)private;
-    private->artlen = sb.st_size;
-    if (innconf->articlemmap) {
-       if ((private->artbase = mmap(NULL, sb.st_size, PROT_READ, MAP_SHARED, fd, 0)) == MAP_FAILED) {
-           SMseterror(SMERR_UNDEFINED, NULL);
-           syslog(L_ERROR, "tradspool: could not mmap article: %m");
-           free(art->private);
-           free(art);
-           close(fd);
-           return NULL;
-       }
-        if (amount == RETR_ALL)
-            madvise(private->artbase, sb.st_size, MADV_WILLNEED);
-        else
-            madvise(private->artbase, sb.st_size, MADV_SEQUENTIAL);
-
-       /* consider coexisting both wireformatted and nonwireformatted */
-       p = memchr(private->artbase, '\n', private->artlen);
-       if (p == NULL || p == private->artbase) {
-           SMseterror(SMERR_UNDEFINED, NULL);
-           syslog(L_ERROR, "tradspool: could not mmap article: %m");
-           munmap(private->artbase, private->artlen);
-           free(art->private);
-           free(art);
-           close(fd);
-           return NULL;
-       }
-       if (p[-1] == '\r') {
-           private->mmapped = true;
-       } else {
-           wfarticle = ToWireFmt(private->artbase, private->artlen, &wflen);
-           munmap(private->artbase, private->artlen);
-           private->artbase = wfarticle;
-           private->artlen = wflen;
-           private->mmapped = false;
-       }
-    } else {
-       private->mmapped = false;
-       private->artbase = xmalloc(private->artlen);
-       if (read(fd, private->artbase, private->artlen) < 0) {
-           SMseterror(SMERR_UNDEFINED, NULL);
-           syslog(L_ERROR, "tradspool: could not read article: %m");
-           free(private->artbase);
-           free(art->private);
-           free(art);
-           close(fd);
-           return NULL;
-       }
-       p = memchr(private->artbase, '\n', private->artlen);
-       if (p == NULL || p == private->artbase) {
-           SMseterror(SMERR_UNDEFINED, NULL);
-           syslog(L_ERROR, "tradspool: could not mmap article: %m");
-           free(art->private);
-           free(art);
-           close(fd);
-           return NULL;
-       }
-       if (p[-1] != '\r') {
-           /* need to make a wireformat copy of the article */
-           wfarticle = ToWireFmt(private->artbase, private->artlen, &wflen);
-           free(private->artbase);
-           private->artbase = wfarticle;
-           private->artlen = wflen;
-       }
-    }
-    close(fd);
-    
-    private->ngtp = NULL;
-    private->curdir = NULL;
-    private->curdirname = NULL;
-    private->nextindex = -1;
-
-    if (amount == RETR_ALL) {
-       art->data = private->artbase;
-       art->len = private->artlen;
-       return art;
-    }
-    
-    if (((p = wire_findbody(private->artbase, private->artlen)) == NULL)) {
-       if (private->mmapped)
-           munmap(private->artbase, private->artlen);
-       else
-           free(private->artbase);
-       SMseterror(SMERR_NOBODY, NULL);
-       free(art->private);
-       free(art);
-       return NULL;
-    }
-
-    if (amount == RETR_HEAD) {
-       art->data = private->artbase;
-       art->len = p - private->artbase;
-       return art;
-    }
-
-    if (amount == RETR_BODY) {
-       art->data = p;
-       art->len = private->artlen - (p - private->artbase);
-       return art;
-    }
-    SMseterror(SMERR_UNDEFINED, "Invalid retrieve request");
-    if (private->mmapped)
-       munmap(private->artbase, private->artlen);
-    else
-       free(private->artbase);
-    free(art->private);
-    free(art);
-    return NULL;
-}
-
-    
-ARTHANDLE *
-tradspool_retrieve(const TOKEN token, const RETRTYPE amount) {
-    char *path;
-    ARTHANDLE *art;
-    static TOKEN ret_token;
-
-    if (token.type != TOKEN_TRADSPOOL) {
-       SMseterror(SMERR_INTERNAL, NULL);
-       return NULL;
-    }
-
-    if ((path = TokenToPath(token)) == NULL) {
-       SMseterror(SMERR_NOENT, NULL);
-       return NULL;
-    }
-    if ((art = OpenArticle(path, amount)) != (ARTHANDLE *)NULL) {
-        ret_token = token;
-        art->token = &ret_token;
-    }
-    free(path);
-    return art;
-}
-
-void
-tradspool_freearticle(ARTHANDLE *article)
-{
-    PRIV_TRADSPOOL *private;
-
-    if (article == NULL)
-        return;
-
-    if (article->private) {
-       private = (PRIV_TRADSPOOL *) article->private;
-       if (private->mmapped)
-           munmap(private->artbase, private->artlen);
-       else
-           free(private->artbase);
-       if (private->curdir)
-           closedir(private->curdir);
-       free(private->curdirname);
-        free(private);
-    }
-    free(article);
-}
-
-bool 
-tradspool_cancel(TOKEN token) {
-    char **xrefs;
-    char *xrefhdr;
-    ARTHANDLE *article;
-    unsigned int numxrefs;
-    char *ng, *p;
-    char *path, *linkpath;
-    unsigned int i;
-    bool result = true;
-    unsigned long artnum;
-    size_t length;
-
-    if ((path = TokenToPath(token)) == NULL) {
-       SMseterror(SMERR_UNDEFINED, NULL);
-       free(path);
-       return false;
-    }
-    /*
-    **  Ooooh, this is gross.  To find the symlinks pointing to this article,
-    ** we open the article and grab its Xref line (since the token isn't long
-    ** enough to store this info on its own).   This is *not* going to do 
-    ** good things for performance of fastrm...  -- rmtodd
-    */
-    if ((article = OpenArticle(path, RETR_HEAD)) == NULL) {
-       free(path);
-       SMseterror(SMERR_UNDEFINED, NULL);
-        return false;
-    }
-
-    xrefhdr = wire_findheader(article->data, article->len, "Xref");
-    if (xrefhdr == NULL) {
-       /* for backwards compatibility; there is no Xref unless crossposted
-          for 1.4 and 1.5 */
-       if (unlink(path) < 0) result = false;
-       free(path);
-       tradspool_freearticle(article);
-        return result;
-    }
-
-    if ((xrefs = CrackXref(xrefhdr, &numxrefs)) == NULL || numxrefs == 0) {
-        if (xrefs != NULL)
-            free(xrefs);
-       free(path);
-       tradspool_freearticle(article);
-        SMseterror(SMERR_UNDEFINED, NULL);
-        return false;
-    }
-
-    tradspool_freearticle(article);
-    for (i = 1 ; i < numxrefs ; ++i) {
-       if ((p = strchr(xrefs[i], ':')) == NULL) continue;
-       *p++ = '\0';
-       ng = xrefs[i];
-       DeDotify(ng);
-       artnum = atol(p);
-
-        length = strlen(innconf->patharticles) + strlen(ng) + 32;
-       linkpath = xmalloc(length);
-       snprintf(linkpath, length, "%s/%s/%lu", innconf->patharticles, ng,
-                 artnum);
-       /* repeated unlinkings of a crossposted article may fail on account
-          of the file no longer existing without it truly being an error */
-       if (unlink(linkpath) < 0)
-           if (errno != ENOENT || i == 1)
-               result = false;
-       free(linkpath);
-    }
-    if (unlink(path) < 0)
-       if (errno != ENOENT || numxrefs == 1)
-           result = false;
-    free(path);
-    for (i = 0 ; i < numxrefs ; ++i) free(xrefs[i]);
-    free(xrefs);
-    return result;
-}
-
-   
-/*
-** Find entries for possible articles in dir. "dir" (directory name "dirname").
-** The dirname is needed so we can do stats in the directory to disambiguate
-** files from symlinks and directories.
-*/
-
-static struct dirent *
-FindDir(DIR *dir, char *dirname) {
-    struct dirent *de;
-    int i;
-    bool flag;
-    char *path;
-    struct stat sb;
-    size_t length;
-    unsigned char namelen;
-
-    while ((de = readdir(dir)) != NULL) {
-       namelen = strlen(de->d_name);
-       for (i = 0, flag = true ; i < namelen ; ++i) {
-           if (!CTYPE(isdigit, de->d_name[i])) {
-               flag = false;
-               break;
-           }
-       }
-       if (!flag) continue; /* if not all digits, skip this entry. */
-
-        length = strlen(dirname) + namelen + 2;
-       path = xmalloc(length);
-        strlcpy(path, dirname, length);
-        strlcat(path, "/", length);
-        strlcat(path, de->d_name, length);
-
-       if (lstat(path, &sb) < 0) {
-           free(path);
-           continue;
-       }
-       free(path);
-       if (!S_ISREG(sb.st_mode)) continue;
-       return de;
-    }
-    return NULL;
-}
-
-ARTHANDLE *tradspool_next(const ARTHANDLE *article, const RETRTYPE amount) {
-    PRIV_TRADSPOOL priv;
-    PRIV_TRADSPOOL *newpriv;
-    char *path, *linkpath;
-    struct dirent *de;
-    ARTHANDLE *art;
-    unsigned long artnum;
-    unsigned int i;
-    static TOKEN token;
-    char **xrefs;
-    char *xrefhdr, *ng, *p, *expires, *x;
-    unsigned int numxrefs;
-    STORAGE_SUB        *sub;
-    size_t length;
-
-    if (article == NULL) {
-       priv.ngtp = NULL;
-       priv.curdir = NULL;
-       priv.curdirname = NULL;
-       priv.nextindex = -1;
-    } else {
-       priv = *(PRIV_TRADSPOOL *) article->private;
-       free(article->private);
-       free((void*)article);
-       if (priv.artbase != NULL) {
-           if (priv.mmapped)
-               munmap(priv.artbase, priv.artlen);
-           else
-               free(priv.artbase);
-       }
-    }
-
-    while (!priv.curdir || ((de = FindDir(priv.curdir, priv.curdirname)) == NULL)) {
-       if (priv.curdir) {
-           closedir(priv.curdir);
-           priv.curdir = NULL;
-           free(priv.curdirname);
-           priv.curdirname = NULL;
-       }
-
-       /*
-       ** advance ngtp to the next entry, if it exists, otherwise start 
-       ** searching down another ngtable hashchain. 
-       */
-       while (priv.ngtp == NULL || (priv.ngtp = priv.ngtp->next) == NULL) {
-           /*
-           ** note that at the start of a search nextindex is -1, so the inc.
-           ** makes nextindex 0, as it should be.
-           */
-           priv.nextindex++;
-           if (priv.nextindex >= NGT_SIZE) {
-               /* ran off the end of the table, so return. */
-               return NULL;
-           }
-           priv.ngtp = NGTable[priv.nextindex];
-           if (priv.ngtp != NULL)
-               break;
-       }
-
-        priv.curdirname = concatpath(innconf->patharticles, priv.ngtp->ngname);
-       priv.curdir = opendir(priv.curdirname);
-    }
-
-    path = concatpath(priv.curdirname, de->d_name);
-    i = strlen(priv.curdirname);
-    /* get the article number while we're here, we'll need it later. */
-    artnum = atol(&path[i+1]);
-
-    art = OpenArticle(path, amount);
-    if (art == (ARTHANDLE *)NULL) {
-       art = xmalloc(sizeof(ARTHANDLE));
-       art->type = TOKEN_TRADSPOOL;
-       art->data = NULL;
-       art->len = 0;
-       art->private = xmalloc(sizeof(PRIV_TRADSPOOL));
-       art->expires = 0;
-       art->groups = NULL;
-       art->groupslen = 0;
-       newpriv = (PRIV_TRADSPOOL *) art->private;
-       newpriv->artbase = NULL;
-    } else {
-       /* Skip linked (not symlinked) crossposted articles.
-
-           This algorithm is rather questionable; it only works if the first
-           group/number combination listed in the Xref header is the
-           canonical path.  This will always be true for spools created by
-           this implementation, but for traditional INN 1.x servers,
-           articles are expired indepedently from each group and may expire
-           out of the first listed newsgroup before other groups.  This
-           algorithm will orphan such articles, not adding them to history.
-
-           The bit of skipping articles by setting the length of the article
-           to zero is also rather suspect, and I'm not sure what
-           implications that might have for the callers of SMnext.
-
-           Basically, this whole area really needs to be rethought. */
-       xrefhdr = wire_findheader(art->data, art->len, "Xref");
-       if (xrefhdr != NULL) {
-           if ((xrefs = CrackXref(xrefhdr, &numxrefs)) == NULL || numxrefs == 0) {
-               art->len = 0;
-           } else {
-               /* assumes first one is the original */
-               if ((p = strchr(xrefs[1], ':')) != NULL) {
-                   *p++ = '\0';
-                   ng = xrefs[1];
-                   DeDotify(ng);
-                   artnum = atol(p);
-
-                    length = strlen(innconf->patharticles) + strlen(ng) + 32;
-                   linkpath = xmalloc(length);
-                   snprintf(linkpath, length, "%s/%s/%lu",
-                             innconf->patharticles, ng, artnum);
-                   if (strcmp(path, linkpath) != 0) {
-                       /* this is linked article, skip it */
-                       art->len = 0;
-                   }
-                   free(linkpath);
-               }
-           }
-           for (i = 0 ; i < numxrefs ; ++i) free(xrefs[i]);
-           free(xrefs);
-           if (innconf->storeonxref) {
-               /* skip path element */
-               if ((xrefhdr = strchr(xrefhdr, ' ')) == NULL) {
-                   art->groups = NULL;
-                   art->groupslen = 0;
-               } else {
-                   for (xrefhdr++; *xrefhdr == ' '; xrefhdr++);
-                   art->groups = xrefhdr;
-                   for (p = xrefhdr ; (*p != '\n') && (*p != '\r') ; p++);
-                   art->groupslen = p - xrefhdr;
-               }
-           }
-       }
-       if (xrefhdr == NULL || !innconf->storeonxref) {
-            ng = wire_findheader(art->data, art->len, "Newsgroups");
-           if (ng == NULL) {
-               art->groups = NULL;
-               art->groupslen = 0;
-           } else {
-               art->groups = ng;
-               for (p = ng ; (*p != '\n') && (*p != '\r') ; p++);
-               art->groupslen = p - ng;
-           }
-       }
-        expires = wire_findheader(art->data, art->len, "Expires");
-        if (expires == NULL) {
-           art->expires = 0;
-       } else {
-            /* optionally parse expire header */
-            for (p = expires + 1; (*p != '\n') && (*(p - 1) != '\r'); p++);
-            x = xmalloc(p - expires);
-            memcpy(x, expires, p - expires - 1);
-            x[p - expires - 1] = '\0';
-
-            art->expires = parsedate(x, NULL);
-            if (art->expires == -1)
-                art->expires = 0;
-            else
-                art->expires -= time(0);
-            free(x);
-        }
-       /* for backwards compatibility; assumes no Xref unless crossposted
-          for 1.4 and 1.5: just fall through */
-    }
-    newpriv = (PRIV_TRADSPOOL *) art->private;
-    newpriv->nextindex = priv.nextindex;
-    newpriv->curdir = priv.curdir;
-    newpriv->curdirname = priv.curdirname;
-    newpriv->ngtp = priv.ngtp;
-    
-    if ((sub = SMgetsub(*art)) == NULL || sub->type != TOKEN_TRADSPOOL) {
-       /* maybe storage.conf is modified, after receiving article */
-       token = MakeToken(priv.ngtp->ngname, artnum, 0);
-
-        /* Only log an error if art->len is non-zero, since otherwise we get
-           all the ones skipped via the hard-link skipping algorithm
-           commented above. */
-        if (art->len > 0)
-            syslog(L_ERROR, "tradspool: can't determine class: %s: %s",
-                   TokenToText(token), SMerrorstr);
-    } else {
-       token = MakeToken(priv.ngtp->ngname, artnum, sub->class);
-    }
-    art->token = &token;
-    free(path);
-    return art;
-}
-
-static void
-FreeNGTree(void)
-{
-    unsigned int i;
-    NGTENT *ngtp, *nextngtp;
-
-    for (i = 0 ; i < NGT_SIZE ; i++) {
-        ngtp = NGTable[i];
-        for ( ; ngtp != NULL ; ngtp = nextngtp) {
-           nextngtp = ngtp->next;
-           free(ngtp->ngname);
-           free(ngtp->node);
-           free(ngtp);
-       }
-       NGTable[i] = NULL;
-    }
-    MaxNgNumber = 0;
-    NGTree = NULL;
-}
-
-bool tradspool_ctl(PROBETYPE type, TOKEN *token, void *value) {
-    struct artngnum *ann;
-    unsigned long ngnum;
-    unsigned long artnum;
-    char *ng, *p;
-
-    switch (type) { 
-    case SMARTNGNUM:
-       if ((ann = (struct artngnum *)value) == NULL)
-           return false;
-       CheckNeedReloadDB(false);
-       memcpy(&ngnum, &token->token[0], sizeof(ngnum));
-       memcpy(&artnum, &token->token[sizeof(ngnum)], sizeof(artnum));
-       artnum = ntohl(artnum);
-       ngnum = ntohl(ngnum);
-       ng = FindNGByNum(ngnum);
-       if (ng == NULL) {
-           CheckNeedReloadDB(true);
-           ng = FindNGByNum(ngnum);
-           if (ng == NULL)
-               return false;
-       }
-       ann->groupname = xstrdup(ng);
-        for (p = ann->groupname; *p != 0; p++)
-            if (*p == '/')
-                *p = '.';
-       ann->artnum = (ARTNUM)artnum;
-       return true;
-    default:
-       return false;
-    }       
-}
-
-bool
-tradspool_flushcacheddata(FLUSHTYPE type UNUSED)
-{
-    return true;
-}
-
-void
-tradspool_printfiles(FILE *file, TOKEN token UNUSED, char **xref, int ngroups)
-{
-    int i;
-    char *path, *p;
-
-    for (i = 0; i < ngroups; i++) {
-        path = xstrdup(xref[i]);
-        for (p = path; *p != '\0'; p++)
-            if (*p == '.' || *p == ':')
-                *p = '/';
-        fprintf(file, "%s\n", path);
-        free(path);
-    }
-}
-
-void
-tradspool_shutdown(void) {
-    DumpDB();
-    FreeNGTree();
-}