/*  $Revision: 1.21 $
**
**  Rebuild the history database.
*/
#include <stdio.h>
#include <sys/types.h>
#include "configdata.h"
#include "clibrary.h"
#include <ctype.h>
#include <sys/stat.h>
#if	defined(DO_NEED_TIME)
#include <time.h>
#endif	/* defined(DO_NEED_TIME) */
#include <sys/time.h>
#include <errno.h>
#include "paths.h"
#include "libinn.h"
#include "inndcomm.h"
#include "dbz.h"
#include "qio.h"
#include "macros.h"
#include "mydir.h"


typedef struct _BUFFER {
    char	*Data;
    int		Size;
    int		Used;
} BUFFER;


STATIC char		*ACTIVE = _PATH_ACTIVE;
STATIC char		SPOOL[] = _PATH_SPOOL;
STATIC char		*HISTORYDIR;
STATIC char		HISTORY[] = _PATH_HISTORY;
STATIC char		MESSAGEID[] = "Message-ID:";
STATIC char		EXPIRES[] = "Expires:";
STATIC char		DATE[] = "Date:";
STATIC BOOL		INNDrunning;
STATIC char		*TextFile;
STATIC char		Reason[] = "makehistory is running";
STATIC TIMEINFO		Now;

extern long		dbztagmask();

/*
**  Change to a directory or exit out.
*/
STATIC void
xchdir(where)
    char	*where;
{
    if (chdir(where) < 0) {
	(void)fprintf(stderr, "Can't change to \"%s\", %s\n",
		where, strerror(errno));
	exit(1);
    }
}


/*
**  Remove the DBZ files for the specified base text file.
*/
STATIC void
RemoveDBZFiles(p)
    char	*p;
{
    static char	NOCANDO[] = "Can't remove \"%s\", %s\n";
    char	buff[SMBUF];

    (void)sprintf(buff, "%s.dir", p);
    if (unlink(buff) && errno != ENOENT)
	(void)fprintf(stderr, NOCANDO, buff, strerror(errno));
    (void)sprintf(buff, "%s.pag", p);
    if (unlink(buff) && errno != ENOENT)
	(void)fprintf(stderr, NOCANDO, buff, strerror(errno));
}


/*
**  Rebuild the DBZ file from the text file.
*/
STATIC void
Rebuild(size, IgnoreOld, Overwrite)
    long		size;
    BOOL		IgnoreOld;
    BOOL		Overwrite;
{
    register QIOSTATE	*qp;
    register char	*p;
    register char	*save;
    register long	count;
    long		where;
    datum		key;
    datum		value;
    char		temp[SMBUF];

    xchdir(HISTORYDIR);

    /* Open the text file. */
    qp = QIOopen(TextFile, QIO_BUFFER);
    if (qp == NULL) {
	(void)fprintf(stderr, "Can't open \"%s\", %s\n",
		TextFile, strerror(errno));
	exit(1);
    }

    /* If using the standard history file, force DBZ to use history.n. */
    if (EQ(TextFile, HISTORY) && !Overwrite) {
	(void)sprintf(temp, "%s.n", HISTORY);
	if (link(HISTORY, temp) < 0) {
	    (void)fprintf(stderr, "Can't make temporary link to \"%s\", %s\n",
		    temp, strerror(errno));
	    exit(1);
	}
	RemoveDBZFiles(temp);
	p = temp;
    }
    else {
	temp[0] = '\0';
	RemoveDBZFiles(TextFile);
	p = TextFile;
    }

    /* Open the new database, using the old file if desired and possible. */
    (void)dbzincore(1);
    if (IgnoreOld) {
        /* 70 == average history file line length */
	if (dbzfresh(p, dbzsize(size), HIS_FIELDSEP, 'C', dbztagmask(size*70)) < 0) {
	    (void)fprintf(stderr, "Can't do dbzfresh, %s\n",
		    strerror(errno));
	    if (temp[0])
		(void)unlink(temp);
	    exit(1);
	}
    }
    else {
	if (dbzagain(p, HISTORY) < 0) {
	    (void)fprintf(stderr, "Can't do dbzagain, %s\n", strerror(errno));
	    if (temp[0])
		(void)unlink(temp);
	    exit(1);
	}
    }

    /* Set up the value pointer. */
    value.dptr = (char *)&where;
    value.dsize = sizeof where;

    /* Loop through all lines in the text file. */
    count = 0;
    for (where = QIOtell(qp); (p = QIOread(qp)) != NULL; where = QIOtell(qp)) {
	count++;
	if ((save = strchr(p, HIS_FIELDSEP)) == NULL) {
	    (void)fprintf(stderr, "Bad line #%ld \"%.30s...\"\n", count, p);
	    if (temp[0])
		(void)unlink(temp);
	    exit(1);
	}
	*save = '\0';
	key.dptr = p;
	key.dsize = save - p + 1;
	if (dbzstore(key, value) < 0) {
	    (void)fprintf(stderr, "Can't store \"%s\", %s\n",
		    p, strerror(errno));
	    if (temp[0])
		(void)unlink(temp);
	    exit(1);
	}
    }
    if (QIOerror(qp)) {
	(void)fprintf(stderr, "Can't read \"%s\" near line %ld, %s\n",
		TextFile, count, strerror(errno));
	if (temp[0])
	    (void)unlink(temp);
	exit(1);
    }
    if (QIOtoolong(qp)) {
	(void)fprintf(stderr, "Line %ld is too long\n", count);
	if (temp[0])
	    (void)unlink(temp);
	exit(1);
    }

    /* Close files. */
    QIOclose(qp);
    if (dbmclose() < 0) {
	(void)fprintf(stderr, "Can't close history, %s\n", strerror(errno));
	if (temp[0])
	    (void)unlink(temp);
	exit(1);
    }

    if (temp[0])
	(void)unlink(temp);
}


/*
**  Remove a bad article.
*/
STATIC void
Removeit(name)
    char	*name;
{
    char	*p;

    /* Already in the spool directory, so skip right past the name;
     * the strchr can't return NULL. */
    p = strchr(name, '/') + 1;
    if (unlink(p) < 0 && errno != ENOENT)
	(void)fprintf(stderr, "Can't unlink %s, %s\n",
		name, strerror(errno));
    else
	(void)fprintf(stderr, "Removing %s\n", name);
}


/*
**  Check and parse a Message-ID header line.  Return private space.
*/
static char *
GetMessageID(p)
    register char	*p;
{
    static BUFFER	B;
    int			length;
    static char		NIL[] = "";

    while (ISWHITE(*p))
	p++;
    if (p[0] != '<' || p[strlen(p) - 1] != '>')
	return NIL;

    /* Copy into re-used memory space. */
    length = strlen(p);
    if (B.Size == 0) {
	B.Size = length;
	B.Data = NEW(char, B.Size + 1);
    }
    else if (B.Size < length) {
	B.Size = length;
	RENEW(B.Data, char, B.Size + 1);
    }
    (void)memcpy((POINTER)B.Data, (POINTER)p, (SIZE_T)length + 1);

    for (p = B.Data; *p; p++)
	if (*p == HIS_FIELDSEP)
	    *p = HIS_BADCHAR;
    return B.Data;
}


/*
**  Check and parse an date header line.  Return the new value or
**  zero on error.
*/
static long
GetaDate(p)
    register char	*p;
{
    time_t		t;

    while (ISWHITE(*p))
	p++;
    if ((t = parsedate(p, &Now)) == -1)
	return 0L;
    return (long)t;
}


/*
**  Process a single article.
*/
STATIC void
DoArticle(qp, Sbp, name, out, RemoveBad, Update)
    register QIOSTATE	*qp;
    struct stat		*Sbp;
    char		*name;
    FILE		*out;
    BOOL		RemoveBad;
    BOOL		Update;
{
    static char		IGNORE[] = "Ignoring duplicate %s header in %s\n";
    static char		BADHDR[] = "Bad %s header in %s\n";
    register char	*p;
    char		*MessageID;
    time_t		Arrived;
    time_t		Expires;
    time_t		Posted;
    int			i;
    datum		key;
    datum		value;

    /* Read the file for Message-ID and Expires header. */
    Arrived = Sbp->st_mtime;
    Expires = 0;
    MessageID = NULL;
    Posted = 0;
    while ((p = QIOread(qp)) != NULL && *p != '\0')
	switch (*p) {
	default:
	    break;
	case 'M': case 'm':
	    if (caseEQn(p, MESSAGEID, STRLEN(MESSAGEID))) {
		if (MessageID)
		    (void)fprintf(stderr, IGNORE, MESSAGEID, name);
		else {
		    MessageID = GetMessageID(&p[STRLEN(MESSAGEID)]);
		    if (*MessageID == '\0')
			(void)fprintf(stderr, BADHDR, MESSAGEID, name);
		}
	    }
	    break;
	case 'E': case 'e':
	    if (caseEQn(p, EXPIRES, STRLEN(EXPIRES))) {
		if (Expires > 0)
		    (void)fprintf(stderr, IGNORE, EXPIRES, name);
		else {
		    Expires = GetaDate(&p[STRLEN(EXPIRES)]);
		    if (Expires == 0)
			(void)fprintf(stderr, BADHDR, EXPIRES, name);
		}
	    }
	    break;
	case 'D': case 'd':
	    if (caseEQn(p, DATE, STRLEN(DATE))) {
		if (Posted > 0)
		    (void)fprintf(stderr, IGNORE, DATE, name);
		else {
		    Posted = GetaDate(&p[STRLEN(DATE)]);
		    if (Posted == 0)
			(void)fprintf(stderr, BADHDR, DATE, name);
		}
	    }
	    break;
	}

    /* Check for errors, close the input. */
    if (p == NULL) {
	if (QIOerror(qp)) {
	    (void)fprintf(stderr, "Can't read %s, %s\n",
		    name, strerror(errno));
	    return;
	}
	if (QIOtoolong(qp)) {
	    (void)fprintf(stderr, "Line too long in %s\n", name);
	    return;
	}
    }

    /* Make sure we have everything we need. */
    if (MessageID == NULL || *MessageID == '\0') {
	if (MessageID == NULL)
	    (void)fprintf(stderr, "No %s in %s\n", MESSAGEID, name);
	if (RemoveBad)
	    Removeit(name);
	return;
    }

    if (Update) {
	/* Server already know about this one? */
	key.dptr = MessageID;
	key.dsize = strlen(MessageID) + 1;
	value = dbzfetch(key);
	if (value.dptr != NULL) {
	    return;
	}
    }

    /* Output the line. */
    if (Posted == 0)
	Posted = Arrived;
    if (Expires > 0)
	i = fprintf(out, "%s%c%lu%c%lu%c%lu%c%s\n",
                    MessageID, HIS_FIELDSEP,
                    (unsigned long)Arrived, HIS_SUBFIELDSEP,
                    (unsigned long)Expires,
		    HIS_SUBFIELDSEP, (unsigned long)Posted, HIS_FIELDSEP,
                    name);
    else
	i = fprintf(out, "%s%c%lu%c%s%c%lu%c%s\n",
                    MessageID, HIS_FIELDSEP,
                    (unsigned long)Arrived, HIS_SUBFIELDSEP, HIS_NOEXP,
		    HIS_SUBFIELDSEP, (unsigned long)Posted, HIS_FIELDSEP,
                    name);
    if (i == EOF || ferror(out)) {
	(void)fprintf(stderr, "Can't write history line, %s\n",
		strerror(errno));
	exit(1);
    }
}


/*
**  Process one newsgroup directory.
*/
STATIC void
DoNewsgroup(group, out, RemoveBad, Update)
    char		*group;
    FILE		*out;
    BOOL		RemoveBad;
    BOOL		Update;
{
    register DIR	*dp;
    register DIRENTRY	*ep;
    register QIOSTATE	*qp;
    register char	*p;
    register char	*q;
    struct stat		Sb;
    char		buff[SPOOLNAMEBUFF];
#if	defined(DO_HAVE_SYMLINK)
    char		linkbuff[SPOOLNAMEBUFF];
    int			oerrno;
#endif	/* defined(DO_HAVE_SYMLINK) */

    (void)strcpy(buff, group);
    for (p = group; *p; p++)
	if (*p == '.')
	    *p = '/';
    xchdir(SPOOL);
    if (chdir(group) < 0)
	return;

    if ((dp = opendir(".")) == NULL) {
	(void)fprintf(stderr, "Can't opendir %s, %s\n", group, strerror(errno));
	return;
    }

    q = &buff[strlen(buff)];
    *q++ = '/';

    /* Read all entries in the directory. */
    while ((ep = readdir(dp)) != NULL) {
	p = ep->d_name;
	if (!CTYPE(isdigit, *p) || strspn(p, "0123456789") != strlen(p))
	    continue;
	(void)strcpy(q, p);

	/* Is this a regular file? */
	if (stat(p, &Sb) < 0) {
	    (void)fprintf(stderr, "Can't stat %s, %s\n",
		    buff, strerror(errno));
#if	defined(DO_HAVE_SYMLINK)
	    /* Symlink to nowhere? */
	    oerrno = errno;
	    (void)memset((POINTER)linkbuff, '\0', sizeof linkbuff);
	    if (lstat(p, &Sb) >= 0
	     && readlink(p, linkbuff, sizeof linkbuff - 1) >= 0) {
		linkbuff[sizeof linkbuff - 1] = '\0';
		(void)fprintf(stderr, "Bad symlink %s -> %s, %s\n",
			buff, linkbuff, strerror(oerrno));
		if (RemoveBad)
		    Removeit(buff);
	    }
#endif	/* defined(DO_HAVE_SYMLINK) */
	    continue;
	}
	if (!S_ISREG(Sb.st_mode)) {
	    (void)fprintf(stderr, "%s is not a file\n", buff);
	    continue;
	}
	/* ignore articles added after we started running because:
	** - innd should be catching these already if it is running.
	** - expire may have run and changed to a new history file.
	*/
	if (Sb.st_mtime > Now.time) {
	    continue;
	}

	/* Open the article. */
	if ((qp = QIOopen(p, QIO_BUFFER)) == NULL) {
	    (void)fprintf(stderr, "Can't open %s, %s\n",
		    buff, strerror(errno));
	    continue;
	}
	DoArticle(qp, &Sb, buff, out, RemoveBad, Update);
	QIOclose(qp);
    }

    (void)closedir(dp);
}


/*
**  Tell innd to add a history line.
*/
STATIC BOOL
AddThis(line, Verbose)
    register char	*line;
    BOOL		Verbose;
{
    int			i;
    char		*arrive;
    char		*exp;
    char		*posted;
    char		*paths;
    char		*av[6];

    if ((arrive = strchr(line, HIS_FIELDSEP)) == NULL
     || (exp = strchr(arrive + 1, HIS_SUBFIELDSEP)) == NULL
     || (posted = strchr(exp + 1, HIS_SUBFIELDSEP)) == NULL
     || (paths = strchr(exp + 1, HIS_FIELDSEP)) == NULL) {
	(void)fprintf(stderr, "Got bad history line \"%s\"\n", line);
	return FALSE;
    }
    av[0] = line;
    *arrive = '\0';
    av[1] = arrive + 1;
    *exp = '\0';
    av[2] = exp + 1;
    *posted = '\0';
    av[3] = posted + 1;
    *paths = '\0';
    av[4] = paths + 1;
    av[5] = NULL;
    if (EQ(av[2], HIS_NOEXP))
        av[2] = "0";

    i = ICCcommand(SC_ADDHIST, av, (char **)NULL);
    *arrive = HIS_FIELDSEP;
    *exp = HIS_SUBFIELDSEP;
    *posted = HIS_SUBFIELDSEP;
    *paths = HIS_FIELDSEP;
    if (i < 0) {
        (void)fprintf(stderr, "Can't add history line \"%s\", %s\n",
               line, strerror(errno));
        return FALSE;
    }
    if (Verbose)
	(void)fprintf(stderr, "Added %s\n", line);
    return TRUE;
}



/*
**  Close the server link, and exit.
*/
STATIC NORETURN
ErrorExit(Updating, Stopped)
    BOOL	Updating;
    BOOL	Stopped;
{
    if (Updating) {
	if (!INNDrunning && Stopped && ICCgo(Reason) < 0)
	    (void)fprintf(stderr, "Can't restart server, %s\n",
		    strerror(errno));
	if (ICCclose() < 0)
	    (void)fprintf(stderr, "Can't close link, %s\n",
		    strerror(errno));
    }
    exit(1);
}


/*
**  Print a usage message and exit.
*/
STATIC NORETURN
Usage()
{
    (void)fprintf(stderr,
	    "Usage: makehistory [ -A file ][ -a file ][ -b ][ -f file ][ -i ][ -n ] [ -o ] [ -T dir ][ -v ][ -f file ][ -r ][ -s size ][ -u ]\n");
    exit(1);
    /* NOTREACHED */
}


int
main(ac, av)
    int			ac;
    char		*av[];
{
    register QIOSTATE	*qp;
    register FILE	*out;
    register char	*line;
    register char	*p;
    register char	*q;
    register long	count;
    BUFFER		B;
    long		size;
    int			i;
    BOOL		JustRebuild;
    BOOL		DoRebuild;
    BOOL		IgnoreOld;
    BOOL		Overwrite;
    BOOL		Update;
    BOOL		RemoveBad;
    BOOL		Verbose;
    char		temp[SMBUF];
    char		*TempTextFile;
    char		*tv[2];
    STRING		tmpdir;
    char		*Tflag;
    char		*mode;
    char		*oldtemp;

    /* Set defaults. */
    TextFile = HISTORY;
    DoRebuild = TRUE;
    JustRebuild = FALSE;
    IgnoreOld = FALSE;
    Update = FALSE;
    RemoveBad = FALSE;
    Overwrite = FALSE;
    Verbose = FALSE;
    Tflag = "";
    mode = "w";
    size = 0;
    oldtemp = NULL;
    if ((tmpdir = getenv("TMPDIR")) == NULL)
	tmpdir = _PATH_TMP;
    (void)umask(NEWSUMASK);

    /* Parse JCL. */
    while ((i = getopt(ac, av, "A:a:bf:inors:T:uv")) != EOF)
	switch (i) {
	default:
	    Usage();
	    /* NOTREACHED */
	case 'A':
	    mode = "a";
	    oldtemp = optarg;
	    break;
	case 'a':
	    ACTIVE = optarg;
	    break;
	case 'b':
	    RemoveBad = TRUE;
	    break;
	case 'f':
	    TextFile = optarg;
	    break;
	case 's':
	    size = atol(optarg);
	    /* FALLTHROUGH */
	case 'i':
	    IgnoreOld = TRUE;
	    break;
	case 'n':
	    DoRebuild = FALSE;
	    break;
	case 'o':
	    Overwrite = TRUE;
	    IgnoreOld = TRUE;
	    break;
	case 'r':
	    JustRebuild = TRUE;
	    break;
	case 'T':
	    tmpdir = optarg;
	    Tflag = NEW(char, 3 + strlen(optarg) + 1);
	    (void)sprintf(Tflag, "-T %s", optarg);
	    break;
	case 'u':
	    Update = TRUE;
	    break;
	case 'v':
	    Verbose = TRUE;
	    break;
	}
    ac -= optind;
    av += optind;
    if (ac || (Overwrite && Update) || (Verbose && !Update))
	Usage();
    if ((p = strrchr(TextFile, '/')) == NULL) {
	/* find the default history file directory */
	HISTORYDIR = COPY(_PATH_HISTORY);
	p = strrchr(HISTORYDIR, '/');
	if (p != NULL) {
	    *p = '\0';
	}
    } else {
	*p = '\0';
	HISTORYDIR = COPY(TextFile);
	*p = '/';
    }

    /* If we're not gonna scan the database, get out. */
    if (JustRebuild) {
	Rebuild(size, IgnoreOld, Overwrite);
	exit(0);
    }

    /* Get the time.  Only get it once, which is good enough. */
    if (GetTimeInfo(&Now) < 0) {
	(void)fprintf(stderr, "Can't get the time, %s\n", strerror(errno));
	exit(1);
    }

    /* Open history file. */
    xchdir(HISTORYDIR);

    if (Update || !Overwrite) {
	(void)sprintf(temp, "%s/histXXXXXX", tmpdir);
	(void)mktemp(temp);
	TempTextFile = oldtemp ? COPY(oldtemp) : COPY(temp);
    }
    else
	TempTextFile = NULL;
    if (Update) {
	if (ICCopen() < 0) {
	    (void)fprintf(stderr, "Can't talk to server, %s\n",
		    strerror(errno));
	    exit(1);
	}
	tv[0] = Reason;
	tv[1] = NULL;
	if (DoRebuild && ICCcommand(SC_THROTTLE, tv, (char **)NULL) < 0) {
	    (void)fprintf(stderr, "Can't throttle innd, %s\n",
		    strerror(errno));
	    exit(1);
	}
	if (dbminit(TextFile) == -1) {
	    (void)fprintf(stderr, "Can't open dbz file, %s\n",
		    strerror(errno));
	    ErrorExit(TRUE, DoRebuild);
	}
    }

    if ((out = fopen(TempTextFile ? TempTextFile : TextFile, mode)) == NULL) {
	(void)fprintf(stderr, "Can't write to history file, %s\n",
		strerror(errno));
	exit(1);
    }

    /* Start scanning the directories. */
    if ((qp = QIOopen(ACTIVE, QIO_BUFFER)) == NULL) {
	(void)fprintf(stderr, "Can't open %s, %s\n", ACTIVE, strerror(errno));
	exit(1);
    }
    for (count = 1; (line = QIOread(qp)) != NULL; count++) {
	if ((p = strchr(line, ' ')) == NULL) {
	    (void)fprintf(stderr, "Bad line %ld, \"%s\"\n", count, line);
	    continue;
	}
	*p = '\0';
	DoNewsgroup(line, out, RemoveBad, Update);
    }
    /* Test error conditions; QIOtoolong shouldn't happen. */
    if (QIOtoolong(qp)) {
	(void)fprintf(stderr, "Line %ld is too long\n", count);
	ErrorExit(Update, DoRebuild);
    }
    if (QIOerror(qp)) {
	(void)fprintf(stderr, "Can't read %s around line %ld, %s\n",
		ACTIVE, count, strerror(errno));
	ErrorExit(Update, DoRebuild);
    }
    if (fflush(out) == EOF || ferror(out) || fclose(out) == EOF) {
	(void)fprintf(stderr, "Can't close history file, %s\n",
		strerror(errno));
	ErrorExit(Update, DoRebuild);
    }

    /* Move. */
    xchdir(HISTORYDIR);

    if (Update) {
	INNDrunning = TRUE;
	if (dbmclose() < 0) {
	    (void)fprintf(stderr, "Can't close DBZ file, %s\n",
		    strerror(errno));
	    ErrorExit(Update, DoRebuild);
	}
	if (DoRebuild && ICCgo(Reason) < 0) {
	    (void)fprintf(stderr, "Can't restart innd, %s\n", strerror(errno));
	    ErrorExit(Update, DoRebuild);
	}
    }

    /* Make a temporary file, sort the text file into it. */
    (void)sprintf(temp, "%s/histXXXXXX", tmpdir);
    (void)mktemp(temp);
    i = 50 + strlen(TempTextFile ? TempTextFile : TextFile) + strlen(temp);
    p = NEW(char, i);
    (void)sprintf(p, "exec sort %s -t'%c' +1n -o %s %s",
	    Tflag, HIS_FIELDSEP, temp, TempTextFile ? TempTextFile : TextFile);

    i = system(p) >> 8;
    if (i != 0) {
	(void)fprintf(stderr, "Can't sort history file (exit %d), %s\n",
		i, strerror(errno));
	ErrorExit(Update, DoRebuild);
    }
    DISPOSE(p);

    if (TempTextFile) {
        if (unlink(TempTextFile) && errno != ENOENT)
	    (void)fprintf(stderr, "Can't remove \"%s\", %s\n",
		    TempTextFile, strerror(errno));
	DISPOSE(TempTextFile);
	TempTextFile = NULL;
    }

    /* Open the sorted file, get ready to write the final text file. */
    if ((qp = QIOopen(temp, QIO_BUFFER)) == NULL) {
	(void)fprintf(stderr, "Can't open work file \"%s\", %s\n",
		temp, strerror(errno));
	ErrorExit(Update, DoRebuild);
    }
    if (!Update && (out = fopen(TextFile, "w")) == NULL) {
	(void)fprintf(stderr, "Can't start writing %s, %s\n",
		TextFile, strerror(errno));
	(void)fprintf(stderr, "Work file %s untouched.\n", temp);
	ErrorExit(Update, DoRebuild);
    }

    /* Get space to keep the joined history lines. */
    B.Size = 100;
    B.Used = 0;
    B.Data = NEW(char, B.Size);
    p = COPY("");

    /* Read the sorted work file. */
    for (count = 0; (line = QIOread(qp)) != NULL; ) {
	count++;
	if ((q = strchr(line, HIS_FIELDSEP)) == NULL) {
	    (void)fprintf(stderr, "Work file line %ld had bad format\n",
		    count);
	    ErrorExit(Update, DoRebuild);
	}
	*q = '\0';
	if (EQ(p, line)) {
	    /* Same Message-ID as last time -- get filename */
	    if ((q = strchr(q + 1, HIS_FIELDSEP)) == NULL) {
		(void)fprintf(stderr, "Work file line %ld missing filename\n",
			count);
		ErrorExit(Update, DoRebuild);
	    }
	    i = strlen(q);
	    if (B.Size < B.Used + i + 3) {
		B.Size = B.Used + i + 3;
		RENEW(B.Data, char, B.Size);
	    }
	    *q = ' ';
	    (void)strcpy(&B.Data[B.Used], q);
	    B.Used += i;
	}
	else {
	    /* Different Message-ID; end old line, start new one. */
	    if (*p) {
		if (!Update)
		    (void)fprintf(out, "%s\n", B.Data);
		else if (!AddThis(B.Data, Verbose))
		    ErrorExit(Update, DoRebuild);
	    }
	    DISPOSE(p);
	    p = COPY(line);

	    *q = HIS_FIELDSEP;
	    i = strlen(line);
	    if (B.Size <= i) {
		B.Size = i + 2;
		RENEW(B.Data, char, B.Size);
	    }
	    (void)strcpy(B.Data, line);
	    B.Used = i;
	}
	if (!Update && ferror(out)) {
	    (void)fprintf(stderr, "Can't write output from line %ld, %s\n",
		    count, strerror(errno));
	    ErrorExit(Update, DoRebuild);
	}
    }

    /* Check for errors and close. */
    if (QIOtoolong(qp)) {
	(void)fprintf(stderr, "Line %ld is too long\n", count);
	ErrorExit(Update, DoRebuild);
    }
    if (QIOerror(qp)) {
	(void)fprintf(stderr, "Can't read work file, %s\n", strerror(errno));
	ErrorExit(Update, DoRebuild);
    }
    QIOclose(qp);
    if (unlink(temp) && errno != ENOENT)
	(void)fprintf(stderr, "Can't remove \"%s\", %s\n",
		temp, strerror(errno));

    if (*p) {
	/* Add tail end of last line. */
	if (!Update)
	    (void)fprintf(out, "%s\n", B.Data);
	else if (!AddThis(B.Data, Verbose))
	    ErrorExit(Update, DoRebuild);
    }

    /* Close the output file. */
    if (!Update) {
        if (fflush(out) == EOF || fclose(out) == EOF) {
	    (void)fprintf(stderr, "Can't close history file, %s\n",
		    strerror(errno));
	    ErrorExit(Update, DoRebuild);
        }
	if (DoRebuild)
	    Rebuild(size ? size : count, TRUE, Overwrite);
    }
    else if (ICCclose() < 0)
	(void)fprintf(stderr, "Can't close link, %s\n", strerror(errno));

    exit(0);
    /* NOTREACHED */
}
