[comp.sources.unix] v19i085: Cnews production release, Part08/19

rsalz@uunet.uu.net (Rich Salz) (06/28/89)

Submitted-by: utzoo!henry
Posting-number: Volume 19, Issue 85
Archive-name: cnews2/part08

: ---CUT HERE---
echo 'expire/expire.c':
sed 's/^X//' >'expire/expire.c' <<'!'
X/*
X * expire - expire old news
X *
X * One modest flaw:  links are not preserved in archived copies, i.e. you
X * get multiple copies of multiply-posted articles.  Since link preservation
X * is arbitrarily hard when control files get complex, to hell with it.
X */
X
X#include <stdio.h>
X#include <ctype.h>
X#include <string.h>
X#include <errno.h>
X#include <time.h>
X#include <signal.h>
X#include <sys/types.h>
X#include <sys/timeb.h>
X#include <sys/stat.h>
X#include "libc.h"
X#include "news.h"
X#include "config.h"
X#include "fgetmfs.h"
X
X#ifndef EPOCH
X#define	EPOCH	((time_t)0)
X#endif
X
X
X/* structure for dbm */
Xtypedef struct {
X	char *dptr;
X	int dsize;
X} datum;
X
X#define	DAY	((double)24*60*60)
X
X/* structure for expiry-control records */
Xstruct ctl {
X	struct ctl *next;
X	char *groups;		/* newsgroups */
X	int ismod;		/* moderated? */
X#		define	UNMOD	'u'
X#		define	MOD	'm'
X#		define	EITHER	'x'
X	time_t retain;		/* earliest arrival date not expired */
X	time_t normal;		/* earliest not expired in default case */
X	time_t purge;		/* latest arrival date always expired */
X	char *dir;		/* Archive dir or NULL. */
X};
X
X/* header for internal form of control file */
Xstruct ctl *ctls = NULL;
Xstruct ctl *lastctl = NULL;
X
X/*
X * Headers for lists by newsgroup, derived (mostly) from active file.
X * Hashing is by length of newsgroup name; this is quick and works well,
X * and there is no simple variation that does much better.
X */
X#define	NHASH	80
Xstruct ctl *ngs[NHASH] = { NULL };
X
Xstruct ctl *holdover = NULL;	/* "/expired/" control record */
Xstruct ctl *bounds = NULL;	/* "/bounds/" control record */
X
Xint debug = 0;			/* for inews routines */
Xint expdebug = 0;		/* expire debugging */
X
Xint printexpiring = 0;		/* print info line for expiring articles? */
Xchar *defarch = NULL;		/* default archive dir */
Xint spacetight = 0;		/* error-recovery actions remove evidence? */
X
Xchar *subsep = "~";		/* subfield separator in middle field */
Xint checkonly = 0;		/* check control information only */
Xint testing = 0;		/* testing only, leave articles alone */
Xint leaders = 0;		/* only first link ("leader") is hard link */
Xint verbose = 0;		/* report statistics */
X
Xlong nkept = 0;			/* count of articles not expired */
Xlong ngone = 0;			/* count of articles removed (no links left) */
Xlong nresid = 0;		/* count of residual entries kept */
Xlong narched = 0;		/* count of links archived */
Xlong njunked = 0;		/* count of links just removed */
Xlong nmissing = 0;		/* count of links missing at cp/rm time */
X
Xchar dont[] = "don't";		/* magic cookie for whereexpire() return */
X
Xtime_t now;
Xstruct timeb ftnow;		/* ftime() result for getdate() */
X#define	NODATE	((time_t)(-1))	/* time_t value indicating date not given */
X
Xchar subject[200] = "";		/* Subject line for -p, minus header */
X
X/* Buffer etc. for readline and friends. */
Xchar rlbuf[BUFSIZ];
Xint rlnleft = 0;
Xchar *rest;
Xint nlocked = 0;		/* has readline() locked the news system? */
X
X/*
X * Archive-copying buffer.
X * 8KB buffer is large enough to take most articles at one gulp,
X * and also large enough for virtual certainty of getting the
X * Subject: line in the first bufferload.
X */
X#ifdef SMALLMEM
Xchar abuf[2*1024];		/* expire reported to be tight on 11 */
X#else
Xchar abuf[8*1024];
X#endif
X
Xchar *progname;
X
Xextern int errno;
Xextern long atol();
Xextern double atof();
Xextern char *malloc();
Xextern struct tm *gmtime();
Xextern time_t time();
X
Xextern time_t getdate();
X
X/* forwards */
XFILE *eufopen();
Xvoid eufclose();
Xvoid euclose();
Xchar *whereexpire();
Xtime_t back();
Xvoid checkdir();
Xvoid fail();
Xvoid control();
Xvoid prime();
Xvoid doit();
Xvoid cd();
Xchar *doline();
Xtime_t readdate();
Xchar *doarticle();
Xvoid warning();
Xvoid printstuff();
Xvoid expire();
Xchar *readline();
Xvoid mkparents();
Xvoid getsubj();
Xvoid refill();
Xvoid printlists();
Xvoid pctl();
Xvoid fillin();
Xvoid ctlline();
Xchar *strvsave();
X
X/*
X - main - parse arguments and handle options
X */
Xmain(argc, argv)
Xint argc;
Xchar *argv[];
X{
X	register int c;
X	register int errflg = 0;
X	register FILE *cf;
X	extern int optind;
X	extern char *optarg;
X
X	progname = argv[0];
X	now = time((time_t *)NULL);
X	ftime(&ftnow);
X
X	while ((c = getopt(argc, argv, "pa:sF:cn:tlvd")) != EOF)
X		switch (c) {
X		case 'p':	/* print info line for archived articles */
X			printexpiring = 1;
X			break;
X		case 'a':	/* archive in this directory */
X			defarch = optarg;
X			break;
X		case 's':	/* maximize space during error recovery */
X			spacetight = 1;
X			break;
X		case 'F':	/* subfield separator in middle field */
X			subsep = optarg;
X			break;
X		case 'c':	/* check control-file format only */
X			checkonly = 1;
X			break;
X		case 'n':	/* set value of "now" for testing */
X			now = atol(optarg);
X			break;
X		case 't':	/* testing, do not mess with articles */
X			testing = 1;
X			break;
X		case 'l':	/* leaders */
X			leaders = 1;
X			break;
X		case 'v':	/* verbose -- report some statistics */
X			verbose = 1;
X			break;
X		case 'd':	/* debug */
X			expdebug = 1;
X			break;
X		case '?':
X		default:
X			errflg++;
X			break;
X		}
X	if (errflg || optind < argc-1) {
X		fprintf(stderr, "Usage: %s [-p] [-s] [-c] [-a archdir] [ctlfile]\n",
X								progname);
X		exit(2);
X	}
X	if (expdebug)
X		setbuf(stderr, (char *)NULL);
X
X	if (optind < argc) {
X		cf = eufopen(argv[optind], "r");
X		control(cf);
X		(void) fclose(cf);
X	} else
X		control(stdin);
X	prime(ctlfile("active"));
X
X	if (expdebug)
X		printlists();
X	if (defarch != NULL)
X		checkdir(defarch);
X	if (checkonly)
X		exit(0);
X
X
X	(void) umask(newsumask());
X	doit();			/* side effect: newslock() */
X	newsunlock();
X
X	if (verbose) {
X		fprintf(stderr, "%ld kept, %ld expired\n", nkept, ngone);
X		fprintf(stderr, "%ld residual lines\n", nresid);
X		fprintf(stderr, "%ld links archived, %ld junked, %ld missing\n",
X						narched, njunked, nmissing);
X	}
X	exit(0);
X}
X
X/*
X - control - pick up a control file
X */
Xvoid
Xcontrol(f)
Xregister FILE *f;
X{
X	char line[200];		/* long enough for any sane line */
X	register char *p;
X	void ctlline();
X
X	while (fgets(line, sizeof(line), f) != NULL) {
X		p = &line[strlen(line) - 1];
X		if (*p != '\n')
X			fail("control line `%.30s...' too long", line);
X		*p = '\0';
X		if (line[0] != '#' && line[0] != '\0')
X			ctlline(line);
X	}
X}
X
X/*
X - ctlline - process one control-file line
X */
Xvoid
Xctlline(ctl)
Xchar *ctl;
X{
X	register struct ctl *ct;
X	char *field[4];
X	char datebuf[50];
X	char *dates[3];
X	register int nf;
X	int ndates;
X
X	errno = 0;
X	nf = split(ctl, field, 4, "\t ");
X	if (nf != 4)
X		fail("control line `%.20s...' hasn't got 4 fields", ctl);
X
X	errno = 0;
X	ct = (struct ctl *)malloc(sizeof(struct ctl));
X	if (ct == NULL)
X		fail("out of memory for control list", "");
X
X
X	ct->groups = strsave(field[0]);
X	if (STREQ(field[1], "m"))
X		ct->ismod = MOD;
X	else if (STREQ(field[1], "u"))
X		ct->ismod = UNMOD;
X	else if (STREQ(field[1], "x"))
X		ct->ismod = EITHER;
X	else
X		fail("strange mod field `%s' in control file", field[1]);
X
X	if (strlen(field[2]) > sizeof(datebuf)-1)
X		fail("date specification `%s' too long", field[2]);
X	(void) strcpy(datebuf, field[2]);
X	ndates = split(datebuf, dates, 3, "-");
X	switch (ndates) {
X	case 3:
X		ct->retain = back(atof(dates[0]));
X		ct->normal = back(atof(dates[1]));
X		ct->purge = back(atof(dates[2]));
X		break;
X	case 2:
X		ct->retain = (bounds != NULL) ? bounds->retain : back(0.0);
X		ct->normal = back(atof(dates[0]));
X		ct->purge = back(atof(dates[1]));
X		break;
X	case 1:
X		ct->retain = (bounds != NULL) ? bounds->retain : back(0.0);
X		ct->normal = back(atof(dates[0]));
X		ct->purge = (bounds != NULL) ? bounds->purge : EPOCH;
X		break;
X	default:
X		fail("date processing foulup in `%s'", field[1]);
X		/* NOTREACHED */
X		break;
X	}
X	if (ct->retain < ct->normal || ct->normal < ct->purge)
X		fail("preposterous dates: `%s'", field[2]);
X
X	if (STREQ(field[3], "-"))
X		ct->dir = NULL;
X	else if (STREQ(field[3], "@")) {
X		if (defarch == NULL)
X			fail("@ in control file but no -a", "");
X		ct->dir = defarch;
X	} else {
X		ct->dir = strsave(field[3]);
X		checkdir(ct->dir);
X	}
X
X	/* put it where it belongs */
X	if (STREQ(ct->groups, "/expired/"))
X		holdover = ct;
X	else if (STREQ(ct->groups, "/bounds/"))
X		bounds = ct;
X	else {
X		ct->next = NULL;
X		if (lastctl == NULL)
X			ctls = ct;
X		else
X			lastctl->next = ct;
X		lastctl = ct;
X	}
X}
X
X/*
X - prime - prime control lists from active file
X */
Xvoid
Xprime(afile)
Xchar *afile;
X{
X	register char *line;
X	register FILE *af;
X	register struct ctl *ct;
X#	define	NFACT	4
X	char *field[NFACT];
X	int nf;
X	register int hash;
X
X	af = eufopen(afile, "r");
X	while ((line = fgetms(af)) != NULL) {
X		nf = split(line, field, NFACT, " \t");
X		if (nf != NFACT)
X			fail("bad active-file line for `%s'", field[0]);
X		ct = (struct ctl *)malloc(sizeof(struct ctl));
X		if (ct == NULL)
X			fail("out of memory at newsgroup `%s'", field[0]);
X		ct->groups = strsave(field[0]);
X		ct->ismod = (strchr(field[3], 'm') != NULL) ? MOD : UNMOD;
X		fillin(ct);
X		hash = strlen(field[0]);
X		if (hash > NHASH-1)
X			hash = NHASH-1;
X		ct->next = ngs[hash];
X		ngs[hash] = ct;
X		free(line);
X	}
X	(void) fclose(af);
X}
X
X/*
X - fillin - fill in a ctl struct for a newsgroup from the control-file list
X */
Xvoid
Xfillin(ct)
Xregister struct ctl *ct;
X{
X	register struct ctl *cscan;
X	char grump[100];
X
X	for (cscan = ctls; cscan != NULL; cscan = cscan->next)
X		if (ngmatch(cscan->groups, ct->groups) &&
X				(cscan->ismod == ct->ismod ||
X						cscan->ismod == EITHER)) {
X			ct->retain = cscan->retain;
X			ct->normal = cscan->normal;
X			ct->purge = cscan->purge;
X			ct->dir = cscan->dir;
X			return;
X		}
X
X	/* oooooops... */
X	sprintf(grump, "group `%%s' (%smoderated) not covered by control file",
X					(ct->ismod == MOD) ? "" : "un");
X	fail(grump, ct->groups);
X}
X
X/*
X - doit - file manipulation and master control
X */
Xvoid
Xdoit()
X{
X	register int old;
X	register FILE *new;
X	char *line;
X	long here;
X	register char *nameend;
X	datum lhs;
X	datum rhs;
X	register int ret;
X
X	cd(ctlfile((char *)NULL));
X	old = open("history", 0);
X	if (old < 0)
X		fail("cannot open `%s'", "history");
X	(void) unlink("history.n");
X	(void) unlink("history.n.dir");
X	(void) unlink("history.n.pag");
X	if (spacetight)
X		(void) unlink("history.o");
X	new = eufopen("history.n", "w");
X	(void) fclose(eufopen("history.n.dir", "w"));
X	(void) fclose(eufopen("history.n.pag", "w"));
X	if (dbminit("history.n") < 0)
X		fail("dbminit(history.n) failed", "");
X
X	cd(artfile((char *)NULL));
X	while ((line = readline(old)) != NULL) {
X		line = doline(line);
X		if (line != NULL) {
X			/* extract the message-id */
X			nameend = strchr(line, '\t');
X			if (nameend == NULL) {
X				errno = 0;
X				fail("bad return from doline(): `%.75s'", line);
X			}
X
X			/* make the DBM entry */
X			*nameend = '\0';
X			lhs.dptr = line;
X			lhs.dsize = strlen(line)+1;
X			here = ftell(new);
X			rhs.dptr = (char *)&here;
X			rhs.dsize = sizeof(here);
X			errno = 0;
X			ret = store(lhs, rhs);
X			if (ret < 0)
X				fail("dbm failure on `%s'", line);
X			*nameend = '\t';
X
X			/* and the history entry */
X			fputs(line, new);
X			putc('\n', new);
X			free(line);
X		}
X	}
X	/* side effect of readline() == NULL:  newslock() */
X
X	(void) close(old);
X	eufclose(new, "history.n");
X
X	if (testing)
X		return;
X	cd(ctlfile((char *)NULL));
X	(void) unlink("history.o");
X	if (link("history", "history.o") < 0)
X		fail("can't move history", "");
X	if (unlink("history") < 0)
X		fail("can't finish moving history", "");
X	if (link("history.n", "history") < 0)
X		fail("disaster -- can't reinstate history!", "");
X	if (unlink("history.n") < 0)
X		fail("disaster -- can't unlink history.n!", "");
X	if (unlink("history.dir") < 0)
X		fail("disaster -- can't unlink history.dir!", "");
X	if (unlink("history.pag") < 0)
X		fail("disaster -- can't unlink history.pag!", "");
X	if (link("history.n.dir", "history.dir") < 0)
X		fail("disaster -- can't reinstate history.dir!", "");
X	if (link("history.n.pag", "history.pag") < 0)
X		fail("disaster -- can't reinstate history.pag!", "");
X	if (unlink("history.n.dir") < 0)
X		fail("disaster -- can't unlink history.n.dir!", "");
X	if (unlink("history.n.pag") < 0)
X		fail("disaster -- can't unlink history.n.pag!", "");
X}
X
X/*
X - doline - handle one history line, modifying it if appropriate
X */
Xchar *				/* new (malloced) line; NULL means none */
Xdoline(line)
Xchar *line;			/* malloced; freed here */
X{
X	char *work;
X#	define	NF	3
X	char *field[NF];	/* fields in line */
X	register int nf;
X#	define	NSF	10
X	char *subfield[NSF];	/* subfields in middle field */
X	register int nsf;
X	register time_t recdate;
X	register time_t expdate;
X	char expbuf[25];		/* plenty for decimal time_t */
X	int wasreal;
X
X	if (expdebug) {
X		fputs("\ndoline `", stderr);
X		fputs(line, stderr);
X		fputs("'\n", stderr);
X	}
X
X	/* pull the incoming line apart */
X	work = strsave(line);
X	nf = split(work, field, NF, "\t");
X	if (nf != 3 && nf != 2) {
X		free(work);
X		errno = 0;
X		warning("wrong number of fields in `%.40s...'", line);
X		return(line);	/* leaving the line in the new history file */
X	}
X	if (nf == 2)
X		field[2] = NULL;
X	nsf = split(field[1], subfield, NSF, subsep);
X
X	/* sort out the dates */
X	if (nsf < 2 || STREQ(subfield[1], "-") || STREQ(subfield[1], ""))
X		expdate = NODATE;
X	else {
X		expdate = readdate(subfield[1]);
X		if (expdate == NODATE) {
X			errno = 0;
X			warning("bad expiry date in `%.40s...',", line);
X			warning(" specifically, `%s' -- ignored", subfield[1]);
X		}
X	}
X	recdate = readdate(subfield[0]);
X	if (recdate == NODATE) {
X		free(work);
X		errno = 0;
X		warning("bad arrival date in `%.40s...' -- fix by hand", line);
X		return(line);
X	}
X	free(line);
X	if (expdebug)
X		fprintf(stderr, "rec %ld, expire %ld\n", (long)recdate,
X								(long)expdate);
X
X	/* deal with it */
X	if (nf > 2 && STREQ(field[2], "/"))	/* old C news cancellation */
X		field[2] = NULL;
X	else if (nf > 2 && STREQ(field[2], "cancelled"))	/* B 2.11 */
X		field[2] = NULL;
X	wasreal = (field[2] != NULL);
X	field[2] = doarticle(field[2], recdate, expdate, field[0]);
X	if (wasreal) {
X		if (field[2] == NULL)
X			ngone++;
X		else
X			nkept++;
X	}
X
X	/* construct new line */
X	if (field[2] != NULL || (holdover != NULL &&
X				!shouldgo(recdate, NODATE, holdover))) {
X		if (expdate != NODATE) {
X			sprintf(expbuf, "%ld", (long)expdate);
X			subfield[1] = expbuf;
X		} else
X			subfield[1] = "-";
X		field[1] = strvsave(subfield, nsf, *subsep);
X		line = strvsave(field, 3, '\t');
X		free(field[1]);
X		if (expdebug) {
X			fputs("new line `", stderr);
X			fputs(line, stderr);
X			fputs("'\n", stderr);
X		}
X		if (field[2] == NULL)
X			nresid++;
X	} else
X		line = NULL;
X
X	free(work);
X	return(line);
X}
X
X/*
X - readdate - turn a date into internal form
X */
Xtime_t
Xreaddate(text)
Xchar *text;
X{
X	time_t ret;
X
X	if (strspn(text, "0123456789") == strlen(text))
X		ret = atol(text);
X	else
X		ret = getdate(text, &ftnow);
X	if (ret == -1)
X		ret = NODATE;
X
X	return(ret);
X}
X
X/*
X - doarticle - possibly expire an article
X *
X * Re-uses the space of its first argument.
X */
Xchar *				/* new name list, in space of old, or NULL */
Xdoarticle(oldnames, recdate, expdate, msgid)
Xchar *oldnames;			/* may be destroyed */
Xtime_t recdate;
Xtime_t expdate;
Xchar *msgid;			/* for printstuff() */
X{
X	register char *src;
X	register char *dst;
X	register char *name;
X	register char *dir;
X	register char *p;
X	register char srcc;
X	register int nleft;
X	register int nexpired;
X#	define	NDELIM	" ,"
X
X	if (oldnames == NULL)
X		return(NULL);
X
X	src = oldnames;
X	dst = oldnames;
X	nleft = 0;
X	nexpired = 0;
X	for (;;) {
X		src += strspn(src, NDELIM);
X		name = src;
X		src += strcspn(src, NDELIM);
X		srcc = *src;
X		*src = '\0';
X		if (*name == '\0')
X			break;		/* NOTE BREAK OUT */
X		if (expdebug)
X			fprintf(stderr, "name `%s'\n", name);
X
X		dir = whereexpire(recdate, expdate, name);
X		if (dir != dont && !(leaders && nleft == 0 && srcc != '\0')) {
X			if (expdebug)
X				fprintf(stderr, "expire into `%s'\n",
X					(dir == NULL) ? "(null)" : dir);
X			for (p = strchr(name, '.'); p != NULL;
X							p = strchr(p+1, '.'))
X				*p = '/';
X			expire(name, dir);
X			if (dir != NULL && printexpiring)
X				printstuff(msgid, name, recdate);
X			nexpired++;
X		} else {
X			if (dst != oldnames)
X				*dst++ = ' ';
X			while (*name != '\0')
X				*dst++ = *name++;
X			nleft++;
X		}
X		*src = srcc;
X	}
X
X	if (nleft == 0)
X		return(NULL);
X	*dst++ = '\0';
X	if (leaders && nleft == 1 && nexpired > 0)	/* aging leader */
X		return(doarticle(oldnames, recdate, expdate, msgid));
X	return(oldnames);
X}
X
X/*
X - whereexpire - where should this name expire to, and should it?
X *
X * The "dont" variable's address is used as the don't-expire return value,
X * since NULL means "to nowhere".
X */
Xchar *				/* archive directory, NULL, or dont */
Xwhereexpire(recdate, expdate, name)
Xtime_t recdate;
Xtime_t expdate;
Xchar *name;
X{
X	register char *group;
X	register char *slash;
X	register struct ctl *ct;
X	register int hash;
X
X	group = name;
X	slash = strchr(group, '/');
X	if (slash == NULL) {
X		errno = 0;
X		fail("no slash in article path `%s'", name);
X	} else
X		*slash = '\0';
X	if (strchr(slash+1, '/') != NULL) {
X		*slash = '/';
X		errno = 0;
X		fail("multiple slashes in article path `%s'", name);
X	}
X
X	/* find applicable expiry-control struct (make it if necessary) */
X	hash = strlen(group);
X	if (hash > NHASH-1)
X		hash = NHASH-1;
X	for (ct = ngs[hash]; ct != NULL && !STREQ(ct->groups, group);
X								ct = ct->next)
X		continue;
X	if (ct == NULL) {	/* oops, there wasn't one */
X		if (expdebug)
X			fprintf(stderr, "new group `%s'\n", group);
X		ct = (struct ctl *)malloc(sizeof(struct ctl));
X		if (ct == NULL)
X			fail("out of memory for newsgroup `%s'", group);
X		ct->groups = strsave(group);
X		ct->ismod = UNMOD;	/* unknown -- treat it as mundane */
X		fillin(ct);
X		ct->next = ngs[hash];
X		ngs[hash] = ct;
X	}
X	*slash = '/';
X
X	/* and decide */
X	if (shouldgo(recdate, expdate, ct))
X		return(ct->dir);
X	else
X		return(dont);
X}
X
X/*
X - shouldgo - should article with these dates expire now?
X */
Xint				/* predicate */
Xshouldgo(recdate, expdate, ct)
Xtime_t recdate;
Xtime_t expdate;
Xregister struct ctl *ct;
X{
X	if (recdate >= ct->retain)	/* within retention period */
X		return(0);
X	if (recdate <= ct->purge)	/* past purge date */
X		return(1);
X	if (expdate != NODATE) {
X		if (now >= expdate)	/* past its explicit date */
X			return(1);
X		else
X			return(0);
X	} else {
X		if (recdate < ct->normal)	/* past default date */
X			return(1);
X		else
X			return(0);
X	}
X	/* NOTREACHED */
X}
X
X/*
X - expire - expire an article
X */
Xvoid
Xexpire(name, dir)
Xchar *name;
Xchar *dir;
X{
X	register char *old;
X	register char *new;
X	struct stat stbuf;
X
X	if (testing) {
X		if (dir != NULL)
X			fprintf(stderr, "copy %s %s ; ", name, dir);
X		fprintf(stderr, "remove %s\n", name);
X		return;
X	}
X
X	old = strsave(artfile(name));
X	if (dir != NULL) {
X		if (*dir == '=') {
X			errno = 0;
X			new = strrchr(name, '/');
X			if (new == NULL)
X				fail("no slash in `%s'", name);
X			new++;
X			new = str3save(dir+1, "/", new);
X		} else
X			new = str3save(dir, "/", name);
X		/* cp() usually succeeds, so try it before getting fancy */
X		if (cp(old, new) < 0) {
X			if (stat(old, &stbuf) < 0) {
X				nmissing++;
X				free(old);
X				free(new);
X				return;		/* nonexistent */
X			}
X			if (*dir != '=')
X				mkparents(name, dir);
X			if (cp(old, new) < 0) {
X				warning("can't archive `%s'", name);
X				free(old);
X				free(new);
X				return;		/* without removing it */
X			}
X		}
X		free(new);
X		narched++;
X	}
X	if (unlink(old) < 0) {
X		if (errno != ENOENT)
X			warning("can't remove `%s'", name);
X		else
X			nmissing++;
X	} else if (dir == NULL)
X		njunked++;
X	free(old);
X}
X
X/*
X - cp - try to copy an article
X */
Xint				/* 0 success, -1 failure */
Xcp(src, dst)
Xchar *src;			/* absolute pathnames */
Xchar *dst;
X{
X	register int ret;
X	register int count;
X	register int in, out;
X	register int firstblock = 1;
X
X	in = open(src, 0);
X	if (in < 0)
X		return(-1);
X	out = creat(dst, 0666);
X	if (out < 0) {
X		(void) close(in);
X		return(-1);
X	}
X
X	while ((count = read(in, abuf, sizeof(abuf))) > 0) {
X		ret = write(out, abuf, count);
X		if (ret != count)
X			fail("write error in copying `%s'", src);
X		if (firstblock) {
X			getsubj(abuf, count);
X			firstblock = 0;
X		}
X	}
X	if (count < 0)
X		fail("read error in copying `%s'", src);
X
X	(void) close(in);
X	euclose(out, dst);
X	return(0);
X}
X
X/*
X - getsubj - try to find the Subject: line in a buffer
X *
X * Result goes in "subject", and is never empty.  Tabs become spaces,
X * since they are the output delimiters.
X */
Xvoid
Xgetsubj(buf, bsize)
Xchar *buf;
Xint bsize;
X{
X	register char *scan;
X	register char *limit;
X	register int len;
X	register int clipped;
X	static char sline[] = "Subject:";
X
X	len = strlen(sline);
X	limit = buf + bsize - len;
X	for (scan = buf; scan < limit; scan++)
X		if (STREQN(scan, sline, len) &&
X				(scan == buf || *(scan-1) == '\n')) {
X			scan += len;
X			for (limit = scan; limit < buf+bsize; limit++)
X				if (*limit == '\n')
X					break;
X			while (scan < limit && isspace(*scan))
X				scan++;
X			len = limit-scan;
X			clipped = 0;
X			if (len > sizeof(subject)-1) {
X				len = sizeof(subject) - 1 - strlen("...");
X				clipped = 1;
X			}
X			if (len > 0) {
X				(void) strncpy(subject, scan, len);
X				subject[len] = '\0';
X			} else
X				(void) strcpy(subject, "???");
X			if (clipped)
X				(void) strcat(subject, "...");
X			for (scan = strchr(subject, '\t'); scan != NULL;
X					scan = strchr(scan+1, '\t'))
X				*scan = ' ';
X			return;
X		} else if (*scan == '\n' && scan+1 < limit && *(scan+1) == '\n')
X			break;		/* empty line terminates header */
X
X	/* didn't find one -- fill in *something* */
X	(void) strcpy(subject, "???");
X}
X
X/*
X - mkparents - try to make directories for archiving an article
X *
X * Assumes it can mess with first argument if it puts it all back at the end.
X */
Xvoid
Xmkparents(art, dir)
Xchar *art;			/* name relative to dir */
Xchar *dir;
X{
X	register char *cmd;
X	register char *ocmd;
X	register char *p;
X
X	p = strchr(art, '/');
X	cmd = str3save(binfile((char *)NULL), "/expire/mkadir ", dir);
X	while (p != NULL) {
X		*p = '\0';
X		ocmd = cmd;
X		cmd = str3save(ocmd, " ", art);
X		free(ocmd);
X		*p = '/';
X		p = strchr(p+1, '/');
X	}
X	(void) system(cmd);
X	free(cmd);
X}
X
Xchar *months[12] = {
X	"Jan",
X	"Feb",
X	"Mar",
X	"Apr",
X	"May",
X	"Jun",
X	"Jul",
X	"Aug",
X	"Sep",
X	"Oct",
X	"Nov",
X	"Dec",
X};
X
X/*
X - printstuff - print information about an expiring article
X */
Xvoid
Xprintstuff(msgid, name, recdate)
Xchar *msgid;
Xchar *name;
Xtime_t recdate;
X{
X	struct tm *gmt;
X
X	gmt = gmtime(&recdate);
X	printf("%s\t%s\t%d-%s-%d\t%s\n", name, msgid, gmt->tm_mday,
X			months[gmt->tm_mon], gmt->tm_year+1900, subject);
X}
X
X/*
X - split - divide a line into fields, like awk split()
X */
Xint				/* number of fields */
Xsplit(line, fields, nfmax, sep)
Xchar *line;
Xchar *fields[];
Xint nfmax;
Xchar *sep;
X{
X	register int i;
X	register char *p;
X
X	i = 0;
X	for (p = strtok(line, sep); p != NULL; p = strtok((char *)NULL, sep)) {
X		if (i < nfmax)
X			fields[i] = p;
X		i++;
X	}
X
X	return(i);
X}
X
X/*
X - eufopen - fopen, with fail if doesn't succeed
X */
XFILE *
Xeufopen(name, mode)
Xchar *name;
Xchar *mode;
X{
X	FILE *f;
X	static char grump[50] = "can't open `%s' for `";
X
X	f = fopen(name, mode);
X	if (f == NULL) {
X		(void) strcat(grump, mode);
X		(void) strcat(grump, "'");
X		fail(grump, name);
X	}
X	return(f);
X}
X
X/*
X - eufclose - fclose with failure checking
X */
Xvoid
Xeufclose(f, name)
XFILE *f;
Xchar *name;
X{
X	if (nfclose(f) == EOF)
X		fail("error in closing file `%s'", name);
X}
X
X/*
X - euclose - close with failure checking
X */
Xvoid
Xeuclose(f, name)
Xint f;
Xchar *name;
X{
X	if (fsync(f) < 0 || close(f) < 0)
X		fail("error in closing file `%s'", name);
X}
X
X/*
X - checkdir - check that a directory is real, writeable, and full pathname
X */
Xvoid				/* fail() if not */
Xcheckdir(dir)
Xchar *dir;
X{
X	struct stat stbuf;
X#	define	GRUMP(a,b)	{if (checkonly) {warning(a, b);return;} else fail(a, b);}
X
X	if (*dir == '=')	/* disregard leading '=' */
X		dir++;
X	errno = 0;
X	if (stat(dir, &stbuf) < 0 || (stbuf.st_mode&S_IFMT) != S_IFDIR)
X		GRUMP("`%s' is not a directory", dir);
X	if (access(dir, 02) < 0)
X		GRUMP("directory `%s' not writeable", dir);
X	if (dir[0] != '/')
X		GRUMP("directory `%s' not an absolute pathname", dir);
X}
X
X/*
X - back - get a date n days back, with overflow check
X *
X * Requires that "now" be set first.
X */
Xtime_t
Xback(ndays)
Xdouble ndays;
X{
X	if (ndays > now / DAY)	/* past beginning of time */
X		return((time_t)0);
X	return(now - ndays*DAY);
X}
X
X/*
X - printlists - print control lists for debugging
X */
Xvoid
Xprintlists()
X{
X	register int i;
X	register struct ctl *ct;
X
X	fprintf(stderr, "control file:\n");
X	for (ct = ctls; ct != NULL; ct = ct->next)
X		pctl(ct);
X	fprintf(stderr, "\n");
X
X	for (i = 0; i < NHASH; i++)
X		if (ngs[i] != NULL) {
X			fprintf(stderr, "list %d:\n", i);
X			for (ct = ngs[i]; ct != NULL; ct = ct->next)
X				pctl(ct);
X		}
X	fprintf(stderr, "\n");
X}
X
X/*
X - pctl - print one control-list entry
X */
Xvoid
Xpctl(ct)
Xregister struct ctl *ct;
X{
X#	define	DAYS(x)	((now-(x))/DAY)
X
X	fprintf(stderr, "%s(%c) %.2f-%.2f-%.2f %s\n", ct->groups, ct->ismod,
X			DAYS(ct->retain), DAYS(ct->normal), DAYS(ct->purge),
X			(ct->dir == NULL) ? "(null)" : ct->dir);
X}
X
X/*
X - unprivileged - no-op needed to keep the pathname stuff happy
X */
Xvoid
Xunprivileged()
X{
X}
X
X/*
X - fail - call errunlock, possibly after cleanup
X */
Xvoid
Xfail(s1, s2)
Xchar *s1;
Xchar *s2;
X{
X	if (spacetight) {
X		cd(ctlfile((char *)NULL));
X		(void) unlink("history.n");
X		(void) unlink("history.n.dir");
X		(void) unlink("history.n.pag");
X	}
X	errunlock(s1, s2);
X	/* NOTREACHED */
X}
X
X/*
X - readline - read history line (sans newline), with locking when we hit EOF
X *
X * Minor flaw:  will lose a last line which lacks a newline.
X */
Xchar *				/* NULL is EOF */
Xreadline(fd)
Xint fd;				/* Note descriptor, not FILE *. */
X{
X	register char *line;
X	register int nline;
X	register char *p;
X	register int c;
X	register int n;
X	extern void refill();
X
X	nline = 100;		/* reasonable starter */
X	line = malloc(nline);
X	if (line == NULL)
X		fail("out of space when reading history", "");
X	p = line;
X
X	for (;;) {
X		if (rlnleft <= 0) {
X			refill(fd);
X			if (rlnleft <= 0)	/* refill gave up. */
X				return(NULL);
X		}
X		c = *rest++;
X		rlnleft--;
X
X		if (c == '\n') {
X			*p++ = '\0';
X			return(line);
X		}
X		if (p - line >= nline - 1) {
X			nline = (nline * 3) / 2;
X			n = p - line;
X			line = realloc(line, nline);
X			if (line == NULL)
X				fail("out of memory in readline", "");
X			p = line + n;
X		}
X		*p++ = c;
X	}
X	/* NOTREACHED */
X}
X
X/*
X - refill - refill readline's buffer, with locking on EOF
X */
Xvoid
Xrefill(fd)
Xint fd;
X{
X	register int ret;
X
X	/* Just in case... */
X	if (rlnleft > 0)
X		return;
X
X	/* Try ordinary read. */
X	ret = read(fd, rlbuf, (int)sizeof(rlbuf));
X	if (ret < 0)
X		fail("read error in history", "");
X	if (ret > 0) {
X		rlnleft = ret;
X		rest = rlbuf;
X		return;
X	}
X
X	/* EOF. */
X	if (nlocked)
X		return;		/* We're really done. */
X
X	/* EOF but we haven't locked yet.  Lock and try again. */
X	(void) signal(SIGINT, (sigarg_t)SIG_IGN);
X	(void) signal(SIGQUIT, (sigarg_t)SIG_IGN);
X	(void) signal(SIGHUP, (sigarg_t)SIG_IGN);
X	(void) signal(SIGTERM, (sigarg_t)SIG_IGN);
X	newslock();
X	nlocked = 1;
X	refill(fd);
X}
X
X/*
X - strvsave - sort of like strsave, but for a vector of strings
X */
Xchar *
Xstrvsave(v, nv, delim)
Xchar **v;
Xint nv;
Xchar delim;
X{
X	register char **p;
X	register int i;
X	register char *result;
X	register char *rp;
X	register int len;
X
X	if (nv <= 0)
X		return(strsave(""));
X
X	len = 0;
X	for (i = nv, p = v; i > 0; i--, p++)
X		if (*p != NULL)
X			len += strlen(*p) + 1;
X	result = malloc(len);
X	if (result == NULL)
X		fail("out of memory in strvsave", "");
X
X	rp = result;
X	for (i = nv, p = v; i > 0; i--, p++)
X		if (*p != NULL) {
X			(void) strcpy(rp, *p);
X			rp += strlen(rp);
X			*rp++ = delim;
X		}
X	rp--;
X	*rp = '\0';
X
X	return(result);
X}
!
echo 'expire/histdups':
sed 's/^X//' >'expire/histdups' <<'!'
X# Awk program to merge history lines for the same article in a sorted history
X# file (such as is generated during the mkhistory processing).
XBEGIN { FS = "\t" ; OFS = "\t" ; mesgid = "" }
X{
X	if ($1 != mesgid) {
X		if (mesgid != "")
X			print mesgid, dates, names
X		mesgid = $1
X		dates = $2
X		names = $3
X	} else
X		names = names " " $3
X}
XEND { print mesgid, dates, names }
!
echo 'expire/histinfo.c':
sed 's/^X//' >'expire/histinfo.c' <<'!'
X/*
X * histinfo - print history file lines for articles named on stdin
X */
X
X#include <stdio.h>
X#include <sys/types.h>
X#include <sys/stat.h>		/* for modified time (date received) */
X#include "config.h"
X
X#define MAXLINE 1024
X#define STRLEN(s) (sizeof(s) - 1)	/* s must be a char array */
X
Xchar *progname;
Xint debug;
X
XFILE *efopen();
X
Xchar *spdir;
Xint spdirlen;
X
X/*
X * main - parse arguments and handle options
X */
Xmain(argc, argv)
Xint argc;
Xchar *argv[];
X{
X	int c;
X	int errflg = 0;
X	FILE *in;
X	char inname[MAXLINE];
X	extern int optind;
X	extern char *optarg;
X
X	progname = argv[0];
X	while ((c = getopt(argc, argv, "d")) != EOF)
X		switch (c) {
X		case 'd':
X			++debug;
X			break;
X		default:
X			errflg++;
X			break;
X		}
X	if (optind < argc || errflg) {
X		(void) fprintf(stderr, "usage: %s [-d]\n", progname);
X		exit(2);
X	}
X
X	spdir = artfile((char *)NULL);
X	spdirlen = strlen(spdir);
X	
X	while (fgets(inname, sizeof(inname), stdin) != NULL) {
X		inname[strlen(inname)-1] = '\0';	/* kill newline */
X		in = efopen(inname, "r");
X		process(in, inname);
X		(void) fclose(in);
X	}
X	exit(0);
X}
X
X/*
X * process - process input file
X */
Xprocess(in, inname)
XFILE *in;
Xchar *inname;
X{
X	char *nl, *files;
X	char line[MAXLINE], msgid[MAXLINE], expiry[MAXLINE];
X	char datercv[30];
X	struct stat statb;
X	static char msgidnm[] =  "Message-ID: ";
X	static char expnm[] =    "Expires: ";
X	register char *p;
X	extern char *strrchr();
X	extern char *strchr();
X	extern char *strcpy();
X
X	/* set defaults */
X	(void) strcpy(expiry, "-");
X	(void) strcpy(msgid, "<swill@trash>");
X
X	/* read until EOF or blank line (end of headers) */
X	while (fgets(line, sizeof line, in) != NULL && strcmp(line, "\n") != 0) {
X		if ((nl = strrchr(line, '\n')) != NULL)
X			*nl = '\0';			/* trim newline */
X		if (strncmp(line, msgidnm, STRLEN(msgidnm)) == 0)
X			(void) strcpy(msgid, line+STRLEN(msgidnm));
X		else if (strncmp(line, expnm, STRLEN(expnm)) == 0)
X			(void) strcpy(expiry, line+STRLEN(expnm));
X	}
X
X	/* generate the file name */
X	files = inname;
X	if (strncmp(files, spdir, spdirlen) == 0 &&
X	    files[spdirlen] == '/')
X		files += spdirlen + 1;	/* skip spool dir. & slash */
X
X	/* generate the date received */
X	(void) fstat(fileno(in), &statb);
X	(void) sprintf(datercv, "%ld", statb.st_mtime);
X
X	/* de-tab the message id */
X	for (p = strchr(msgid, '\t'); p != NULL; p = strchr(p, '\t'))
X		*p = ' ';
X
X	/* whomp out the history line */
X	(void) fputs(msgid, stdout);
X	(void) putchar('\t');
X	(void) fputs(datercv, stdout);
X	(void) putchar('~');
X	(void) fputs(expiry, stdout);
X	(void) putchar('\t');
X	(void) fputs(files, stdout);
X	(void) putchar('\n');
X	(void) fflush(stdout);
X}
X
X/*
X * unprivileged - no-op to keep pathname stuff happy
X */
Xvoid
Xunprivileged()
X{
X}
!
echo 'expire/histslash.c':
sed 's/^X//' >'expire/histslash.c' <<'!'
X/*
X * Convert slashed filenames to dotted group/article names in a history
X * file, for use in mkhistory.  Input comes only from stdin.
X */
X#include <stdio.h>
X#include <assert.h>
X
Xchar *progname = "histslash";
X
Xchar buf[4096];			/* paranoia -- ought to be lots */
X
Xmain()
X{
X	register char *scan;
X	register char *last;
X	extern char *strchr();
X
X	while (fgets(buf, sizeof(buf), stdin) != NULL) {
X		scan = strchr(buf, '\t');
X		scan = strchr(scan+1, '\t');
X		scan++;
X		last = NULL;
X		while (*scan != '\0') {
X			if (*scan == '/') {
X				*scan = '.';
X				last = scan;
X			}
X			if (*scan == ' ' || *scan == '\n') {
X				assert(last != NULL);
X				*last = '/';
X			}
X			scan++;
X		}
X		fputs(buf, stdout);
X	}
X}
!
echo 'expire/lowest.c':
sed 's/^X//' >'expire/lowest.c' <<'!'
X/*
X * lowest - print the number of the lowest article in a directory
X */
X
X#include <stdio.h>
X#include <sys/types.h>
X#include <dirent.h>
X
X#define	HUGE	999999999L	/* Bigger than any valid article number. */
X
Xchar *progname;
X
X/*
X - main - parse arguments and handle options
X */
Xmain(argc, argv)
Xint argc;
Xchar *argv[];
X{
X	DIR *d;
X	register struct dirent *dp;
X	long lowest = HUGE;
X	long this;
X	extern long atol();
X
X	progname = argv[0];
X
X	if (argc != 2) {
X		fprintf(stderr, "usage: %s directory\n", progname);
X		exit(2);
X	}
X
X	d = opendir(argv[1]);
X	if (d == NULL) {
X		fprintf(stderr, "%s: can't read directory %s\n", progname,
X								argv[1]);
X		exit(1);
X	}
X	while ((dp = readdir(d)) != NULL) {
X		if (strspn(dp->d_name, "0123456789") == strlen(dp->d_name)) {
X			this = atol(dp->d_name);
X			if (this < lowest)
X				lowest = this;
X		}
X	}
X	closedir(d);
X
X	if (lowest != HUGE)
X		printf("%ld\n", lowest);
X}
!
echo 'expire/mkdbm.c':
sed 's/^X//' >'expire/mkdbm.c' <<'!'
X/*
X * mkdbm - rebuild dbm file for a history file
X *
X * History file on standard input; the dbm database is generated as
X * hist.dir and hist.pag.
X */
X#include <stdio.h>
X#include <string.h>
X#include <ctype.h>
X
Xchar buf[4096];			/* ought to be plenty */
X
X
Xchar *progname = "mkdbm";
Xtypedef struct { char *dptr; int dsize; } datum;
X
Xmain()
X{
X	register char *scan;
X	long place;
X	datum lhs;
X	datum rhs;
X	register int ret;
X
X	close(creat("hist.dir", 0666));
X	close(creat("hist.pag", 0666));
X	dbminit("hist");
X
X	for (;;) {
X		place = ftell(stdin);
X		if (fgets(buf, sizeof(buf), stdin) == NULL)
X			break;
X
X		scan = strchr(buf, '\t');
X		if (scan == NULL || buf[strlen(buf)-1] != '\n') {
X			fprintf(stderr, "bad format: %s", buf);
X			exit(1);
X		}
X		*scan = '\0';
X
X		lhs.dptr = buf;
X		lhs.dsize = strlen(buf) + 1;
X		rhs.dptr = (char *)&place;
X		rhs.dsize = sizeof place;
X		ret = store(lhs, rhs);
X		if (ret < 0)
X			fprintf(stderr, "dbm failure '%s' @ %ld\n", buf, place);
X	}
X	exit(0);
X}
!
echo 'expire/mkhistory':
sed 's/^X//' >'expire/mkhistory' <<'!'
X#! /bin/sh
X# mkhistory - rebuild history file and friends
X
X# =()<. ${NEWSCONFIG-@<NEWSCONFIG>@}>()=
X. ${NEWSCONFIG-/usr/lib/news/bin/config}
X
XPATH=$NEWSCTL/bin:$NEWSBIN/expire:$NEWSBIN:$NEWSPATH ; export PATH
Xumask $NEWSUMASK
X
Xlock="$NEWSCTL/LOCK"		# modify name as appropriate
Xltemp="$NEWSCTL/L.$$"
Xecho $$ >$ltemp
Xtrap "rm -f $ltemp ; exit 0" 0 1 2 15
Xwhile true
Xdo
X	if newslock $ltemp $lock
X	then
X		trap "rm -f $ltemp $lock ; exit 0" 0 1 2 15
X		break
X	fi
X	sleep 30
Xdone
X
Xcd $NEWSARTS
Xfind `ls | egrep -v '\.'` -type f -name '[0-9]*' -print | histinfo | sort |
X	awk -f $NEWSBIN/expire/histdups | histslash >$NEWSCTL/history.n
X
Xcd $NEWSCTL
Xif egrep '^<swill@trash>	' history.n >/dev/null
Xthen
X	echo "$0: <swill@trash> found in history.n -- aborting" >&2
X	exit 1
Xfi
Xmkdbm <history.n
Xmv history history.o &&		# install new ASCII history file
Xmv history.n history &&
Xrm -f history.pag &&		# and related dbm files
Xrm -f history.dir &&
Xmv hist.pag history.pag && mv hist.dir history.dir
!
echo 'expire/upact':
sed 's/^X//' >'expire/upact' <<'!'
X#! /bin/sh
X# Update 3rd field (minimum art. #) of a 4-field active file.
X
X# =()<. ${NEWSCONFIG-@<NEWSCONFIG>@}>()=
X. ${NEWSCONFIG-/usr/lib/news/bin/config}
X
XPATH=$NEWSCTL/bin:$NEWSBIN/expire:$NEWSBIN:$NEWSPATH ; export PATH
Xumask $NEWSUMASK
X
Xcd $NEWSCTL || { echo "$0: can't cd to $NEWSCTL" >&2; exit 1; }
X
X# check active file format
Xset ""`sed 1q active`
Xcase $# in
X4)	;;
X*)	echo "$0: active file has other than 4 fields" >&2
X	exit 1 ;;
Xesac
X
X# lock news system
Xlock="$NEWSCTL/LOCK"
Xltemp="$NEWSCTL/L.$$"
Xecho $$ >$ltemp
Xtrap "rm -f $ltemp ; exit 0" 0 1 2 15
Xwhile true
Xdo
X	if newslock $ltemp $lock
X	then
X		trap "rm -f $ltemp $lock ; exit 0" 0 1 2 15
X		break
X	fi
X	sleep 30
Xdone
X
Xwhile read group max min fourth
Xdo
X	dir=`echo $group | tr . / `	# map ng name to directory name
X	min=
X	if test -d $NEWSARTS/$dir
X	then
X		# min=`lowest $NEWSARTS/$dir`
X		min=`ls $NEWSARTS/$dir | egrep '^[0-9]+$' | sort -nr | tail -1`
X	fi
X	case "$min" in		# no files, so
X	"")	min=$max ;;	# use maximum
X	esac
X	case "$min" in
X	[0-9]|[0-9][0-9]|[0-9][0-9][0-9]|[0-9][0-9][0-9][0-9])	# short
X		min=`expr 00000$min : '.*\(.....\)$'` ;;
X	esac
X
X	echo $group $max $min $fourth
Xdone <active >active.new
X
X# replace active, carefully
Xrm -f active.old
Xln active active.old
Xmv active.new active
X
Xexit 0
!
echo 'expire/superkludge':
sed 's/^X//' >'expire/superkludge' <<'!'
X#! /bin/sh
X# superkludge - implement the stupid Supersedes header for specific groups
X
X# =()<. ${NEWSCONFIG-@<NEWSCONFIG>@}>()=
X. ${NEWSCONFIG-/usr/lib/news/bin/config}
X
XPATH=$NEWSCTL/bin:$NEWSBIN:$NEWSPATH ; export PATH
Xumask $NEWSUMASK
Xtrap "rm -f /tmp/sup*$$ ; exit 0" 0 1 2
X
Xverbose=
Xcase "$1" in
X-v)	verbose=yes ; shift ;;
Xesac
X
Xfor ng
Xdo
X	dir=`echo $ng | sed 's;\.;/;g'`
X	if test ! -d $NEWSARTS/$dir
X	then
X		echo "$0: no directory for newsgroup \`$ng'" >&2
X		exit 1
X	fi
X	cd $NEWSARTS/$dir
X	>/tmp/sup$$
X	for f in `ls | egrep '^[0-9]+$'`
X	do
X		awk 'BEGIN { mid = "xxx" }
X			/^$/ { exit }	# goes to END
X			/^Supersedes:[ 	]/ { sup = $2 }
X			/^Message-ID:[ 	]/ { mid = $2 }
X			END { print FILENAME, mid, sup ; exit }' $f >>/tmp/sup$$
X	done
X	awk 'NF > 3 || $2 !~ /^<.*>$/ || $3 !~ /^(<.*>)?$/' /tmp/sup$$ >/tmp/supx$$
X	if test -s /tmp/supx$$
X	then
X		echo "$0: message-id format problems:" >&2
X		cat /tmp/supx$$ >&2
X		exit 1
X	fi
X	awk 'NF == 3 { print $3 }' /tmp/sup$$ | sort >/tmp/supd$$
X	sort +1 /tmp/sup$$ -o /tmp/sup$$
X	join -j2 2 -o 2.1 /tmp/supd$$ /tmp/sup$$ >/tmp/supx$$
X	rm -f `cat /tmp/supx$$`
X	if test " $verbose" = " yes"
X	then
X		echo "`wc -l </tmp/supx$$` superseded in $ng"
X	fi
Xdone
!
echo 'expire/dircheck':
sed 's/^X//' >'expire/dircheck' <<'!'
X#! /bin/sh
X# dircheck -- checker for expire regression testing
X
Xcase "$1" in
X-n)	invert=yes ; shift ;;
Xesac
X
Xfor f in `sed 's/.*	//;s/\./\//g'`
Xdo
X	if test " $invert" = " "
X	then
X		if test ! -f $1/$f
X		then
X			echo "cannot find $1/$f" >&2
X			exit 1
X		fi
X		it="`cat $1/$f`"
X		if test " $it" = " $f" || expr " $it" : ".*:$f:.*" >/dev/null
X		then
X			: okay
X		else
X			echo "contents of $1/$f are wrong" >&2
X			exit 1
X		fi
X	else
X		if test -f $1/$f
X		then
X			echo "found $1/$f, shouldn't have" >&2
X			exit 1
X		fi
X	fi
Xdone
Xexit 0				# for stupid shells
!
echo 'expire/tgood':
sed 's/^X//' >'expire/tgood' <<'!'
Xcopy foo/2 P/arch ; remove foo/2
Xcopy foo/3 P/arch ; remove foo/3
Xcopy bar/4 P/arch2 ; remove bar/4
Xcopy bar/ugh/5 P/arch ; remove bar/ugh/5
Xremove urp/6
Xremove urp/8
Xremove urp/9
Xcopy foo/11 P/arch ; remove foo/11
Xcopy mod/mod/12 P/arch ; remove mod/mod/12
Xremove mod/unmod/14
Xremove mod/unmod/15
Xcopy bletch/17 =P/arch3/bletch ; remove bletch/17
Xremove urp/98
Xcopy bar/99 P/arch2 ; remove bar/99
Xremove urp/99
!
echo 'expire/pgood':
sed 's/^X//' >'expire/pgood' <<'!'
Xfoo/2	<will2>	1-Jan-1970	???
Xfoo/3	<will3>	1-Jan-1970	???
Xbar/4	<two4>	1-Jan-1970	yes
Xbar/ugh/5	<will5>	1-Jan-1970	???
Xfoo/11	<will11>	1-Jan-1970	???
Xmod/mod/12	<will12>	1-Jan-1970	???
Xbletch/17	<three17>	1-Jan-1970	???
Xbar/99	<multi99>	1-Jan-1970	???
!
echo 'expire/doexpire':
sed 's/^X//' >'expire/doexpire' <<'!'
X#! /bin/sh
X# doexpire - overall administration for expire
X
X# =()<. ${NEWSCONFIG-@<NEWSCONFIG>@}>()=
X. ${NEWSCONFIG-/usr/lib/news/bin/config}
X
XPATH=$NEWSCTL/bin:$NEWSBIN/expire:$NEWSBIN:$NEWSPATH ; export PATH
Xumask $NEWSUMASK
X
Xlock="$NEWSCTL/LOCKexpire"
Xltemp="$NEWSCTL/L.$$"
Xecho $$ >$ltemp
Xtrap "rm -f $ltemp ; exit 0" 0 1 2 15
Xif newslock $ltemp $lock
Xthen
X	trap "rm -f $ltemp $lock ; exit 0" 0 1 2 15
Xelse
X	echo "$0: expire apparently already running" | mail "$NEWSMASTER"
X	exit 1
Xfi
X
Xcd $NEWSCTL
X
Xfirstctl=
Xfirstar=
Xwhile true
Xdo
X	size="`sizeof history history.pag history.dir`"
X	if test " `spacefor $size control`" -le 0
X	then
X		if test " $firstctl" = " "
X		then
X			echo "$0: trouble finding space for work files" |
X							mail "$NEWSMASTER"
X			firstctl=n
X		fi
X	elif test " `spacefor 1 archive`" -le 0
X	then
X		if test " $firstar" = " "
X		then
X			echo "$0: trouble finding space for archiving" |
X							mail "$NEWSMASTER"
X			firstar=n
X		fi
X	else			# enough space both places
X		break
X	fi
X	sleep 600		# and hope it will improve
Xdone
X
Xexpire $* $NEWSCTL/explist 2>/tmp/doex$$
Xif test -s /tmp/doex$$
Xthen
X	(echo 'expire problems:' ; cat /tmp/doex$$ ) | mail "$NEWSMASTER"
X	rm -f /tmp/doex$$
X	exit 1
Xfi
Xrm -f /tmp/doex$$
Xexit 0
!
echo 'h/alloc.h':
sed 's/^X//' >'h/alloc.h' <<'!'
X#ifndef ALLOC_H
X#define ALLOC_H
X
X#ifndef notdef
Xextern char *malloc(), *realloc(), *emalloc();
Xextern char *nemalloc(), *strsave(), *str3save();
X#else				/* notdef */
X/* setup for UT debugging malloc */
X#define MALLOC_TRACE
X#define TRACE
X#include "malloc.h"		/* defines CSRIMALLOC */
Xextern char *_nemalloc(), *_strsave(), *_str3save();
X#define nemalloc(x)		_nemalloc((x), __FILE__, __LINE__)
X#define strsave(x)		_strsave((x), __FILE__, __LINE__)	/* TODO: conflict with malloc.h; fix */
X#define str3save(x, y, z)	_str3save((x), y, z, __FILE__, __LINE__)
X#endif				/* notdef */
X
X#endif				/* ALLOC_H */
!
echo 'h/config.h':
sed 's/^X//' >'h/config.h' <<'!'
X/*
X * configuration-inquiry functions
X */
X
Xextern char *artfile();		/* article pathname, may be relative */
Xextern char *fullartfile();	/* article full pathname */
Xextern char *ctlfile();		/* control-file name */
Xextern char *binfile();		/* program pathname */
Xextern char *newspath();	/* PATH */
Xextern int newsumask();		/* umask */
Xextern char *newsmaster();	/* place to mail complaints to */
X
Xextern void cd();		/* chdir() with errunlock() on failure */
X
Xextern void unprivileged();	/* user-supplied privilege dropper */
!
echo 'h/README':
sed 's/^X//' >'h/README' <<'!'
XThis is C News header files.
!
echo 'h/fgetmfs.h':
sed 's/^X//' >'h/fgetmfs.h' <<'!'
X/* values for fgetmfs flag */
X#define CONT_NO 0		/* no continuations */
X#define CONT_NOSPC 1		/* continue & remove leading whitespace */
X#define CONT_SPC 2		/* continue & keep leading whitespace */
X
Xextern char *fgetmfs();			/* internal interface */
X
X/* external interfaces */
X#define fgetms(fp) fgetmfs(fp, -1, CONT_NO)	/* unbounded read */
X#define cfgetms(fp) fgetmfs(fp, -1, CONT_NOSPC)	/* unbounded read w continuation */
!
echo 'h/libc.h':
sed 's/^X//' >'h/libc.h' <<'!'
X
X#ifndef LIBC_H
X#define LIBC_H
X/*
X * declarations of (supposedly) standard C library functions and types.
X * we don't declare functions that once returned int but may now return void
X * to avoid fatal but spurious compilation errors.  VOID is an attempt to deal
X * with this transition.  sprvalue is similar.
X *
X * The function declarations need to be prototyped to give ansi compilers
X * less gastric distress.
X */
X
X#ifndef VOID
X#define VOID void
X#endif
X#ifndef sprvalue
X/*#define sprvalue char *	/* for stupid archaic 4BSD */
X#define sprvalue int
X#endif
X
X/* Unix system calls */
X/* signal types: tailor to suite local tastes */
Xtypedef VOID (*sigret_t)();
Xtypedef VOID (*sigarg_t)();
X
X#ifdef A_STABLE_WORLD
Xextern VOID _exit();
Xextern int access(), chown(), fork(), link(), mkdir(), umask(), unlink(), wait();
Xextern int alarm();			/* really unsigned? */
Xextern int getuid(), geteuid(), getgid(), getegid();
Xextern int setuid(), setgid();
Xextern int gethostname();
Xextern int execv(), execl(), execve(), execle();
X#endif					/* A_STABLE_WORLD */
Xextern time_t time();			/* sys/timeb.h? */
X
Xextern int errno;			/* errno.h */
Xextern char **environ;
X
X/* C library */
X#ifdef A_STABLE_WORLD
Xextern int strcmp(), strncmp(), strlen();	/* strings.h */
X#endif					/* A_STABLE_WORLD */
Xextern char *strcpy(), *strcat(), *strncpy(), *strncat();	/* strings.h */
Xextern char *index(), *rindex();	/* strings.h */
Xextern char *memcpy();			/* memory.h */
X
X#ifdef A_STABLE_WORLD
Xextern int fflush(), fputs(), ungetc();	/* stdio.h */
Xextern int fread(), fwrite(), fseek();	/* stdio.h */
Xextern int pclose();			/* stdio.h */
Xextern VOID rewind();			/* stdio.h */
Xextern VOID exit();			/* stdio.h */
X#endif					/* A_STABLE_WORLD */
Xextern FILE *popen();			/* stdio.h */
X#ifdef __STDC__
Xextern int printf(char *fmt, ...), fprintf(FILE *, char *fmt, ...); /* stdio.h */
Xextern sprvalue sprintf(char *buf, char *fmt, ...);		/* stdio.h */
X#else					/* __STDC__ */
Xextern int printf(), fprintf();		/* stdio.h */
Xextern sprvalue sprintf();		/* stdio.h */
X#endif					/* __STDC__ */
X
X/* these unfortunately cannot be relied upon to be in the right header */
Xextern struct passwd *getpwnam();	/* pwd.h */
Xextern struct group *getgrnam();	/* grp.h */
Xextern char *ctime();			/* time.h */
X
Xextern long atol();
Xextern char *mktemp();
Xextern char *getenv();
X
X#ifdef A_STABLE_WORLD
Xextern int putenv(), system();
Xextern int getopt();
X#endif					/* A_STABLE_WORLD */
Xextern int optind;
Xextern char *optarg;
X
X#include "alloc.h"			/* ugh */
X#endif					/* LIBC_H */
!
echo 'h/Makefile':
sed 's/^X//' >'h/Makefile' <<'!'
XI = ../include
XINCLS = $(I)/alloc.h $(I)/config.h $(I)/fgetmfs.h $(I)/libc.h $(I)/news.h
X
Xall:	$(INCLS)
X
X$(I)/alloc.h:	alloc.h
X	cp alloc.h $@
X$(I)/config.h:	config.h
X	cp config.h $@
X$(I)/fgetmfs.h:	fgetmfs.h
X	cp fgetmfs.h $@
X$(I)/libc.h:	libc.h
X	cp libc.h $@
X$(I)/news.h:	news.h newshsed
X	sed -f newshsed news.h >$@
X
Xclean:
X	rm -f newshsed
!
echo 'h/news.h':
sed 's/^X//' >'h/news.h' <<'!'
X/*
X * definitions unique to all of C news
X * things marked with qqq are subject to being configured by "build"
X */
X
X/*
X * tunable parameters
X * which actually very seldom need to be tuned
X * in particular, don't get alarmed about MAXCOMP, it's not used for
X *  anything where it matters
X */
X#define MAXPATH 1024		/* max. length of pwd output */
X#define MAXCOMP 14		/* file name component length */
X#define MAXHOST 128		/* max. length of this host's name */
X#define SPOOLTMP ".tmpXXXXXX"	/* template for NEWSARTS temporary link */
X
X
X/* STATIC & FORWARD must agree to avoid redeclarations(!) */
X#define STATIC	static		/* "static" when not debugging|profiling */
X
X/* adapt to compiler limitations */
X#ifdef pdp11
X#define FORWARD			/* "static" except for dmr's 11 compiler */
X#else
X#define FORWARD static		/* "static" except for dmr's 11 compiler */
X#endif
X/* #define void int		/* if your compiler doesn't understand void's */
X/* #define MAXLONG 017777777777L	/* if your compiler lacks "unsigned long" type */
X
X/* adapt to library limitations */
X#define NOSTOREVAL	/* qqq if your dbm store() returns no value (as in orig. v7) */
X
X/* fundamental constants of the implementation */
X#define SMALLMEM	/* qqq for PDP-11s, PDP-8s, IBM PCs, etc. */
X#define	FASTINDEX	/* qqq if string functions are very fast */
X
X/* automatic configuration */
X#ifdef pdp11
X#ifndef SMALLMEM
X#define SMALLMEM
X#endif				/* SMALLMEM */
X#endif				/* pdp11 */
X
X
X/* types */
Xtypedef short statust;
Xtypedef char boolean;
X
X/* status bits */
X#define ST_OKAY		0	/* nothing wrong */
X#define ST_SHORT	(1<<1)	/* article shorter than byte count; truncated? */
X#define ST_ACCESS	(1<<2)	/* no access permission */
X#define ST_REFUSED	(1<<3)	/* article was deliberately refused - OK */
X#define ST_DROPPED	(1<<4)	/* article was accidentally dropped */
X#define ST_DISKFULL	(1<<5)	/* disk full - give up */
X#define ST_JUNKED	(1<<6)	/* article was accepted, but junked */
X
X/* newsgroup specific definitions */
X#define NGSEP ','		/* separates groups */
X#define NGNEG '!'		/* preceding a pattern, negates it */
X#define NGDELIM '.'		/* within a group */
X#define FNDELIM '/'		/* within a group, on disk */
X#define SFNDELIM "/"		/* string of FNDELIM */
X
X/* macros, replacing functions for speed */
X#define max(a,b) ((a) > (b)? (a): (b))
X#define min(a,b) ((a) < (b)? (a): (b))
X#define iswhite(c) ((c) == ' ' || (c) == '\t')
X/* STREQ is an optimised strcmp(a,b)==0 */
X#define STREQ(a, b) ((a)[0] == (b)[0] && strcmp(a, b) == 0)
X/* STREQN is an optimised strncmp(a,b,n)==0; assumes n > 0 */
X#define STREQN(a, b, n) ((a)[0] == (b)[0] && strncmp(a, b, n) == 0)
X#define STRLEN(s) (sizeof (s) - 1)	/* s must be a char array */
X#ifdef FASTINDEX
X#define INDEX(src, chr, dest) (dest) = index(src, chr)
Xextern char *index();
X#else
X#define INDEX(src, chr, dest) \
X	for ((dest) = (src); *(dest) != '\0' && *(dest) != (chr); ++(dest)) \
X		; \
X	if (*(dest) == '\0') \
X		(dest) = NULL		/* N.B.: missing semi-colon */
X#endif
X
X/* macros, of necessity */
X/* nnafree(any **) where "any" is any type; must be a macro */
X#define nnafree(mempp) (*(mempp) != 0? (free((char *)*(mempp)), (*(mempp) = 0)): 0)
X#ifdef lint
Xnnfree(mempp)		/* If *mempp is non-null, free it and zero it. */
Xregister char **mempp;			/* pointer to malloc'ed ptr. */
X{
X	if (*mempp != 0) {
X		free(*mempp);
X		*mempp = 0;
X	}
X}
X#else					/* lint */
X#define nnfree nnafree
X#endif					/* lint */
X
X#define YES 1
X#define NO 0
X
X#define NOTALLHDRS NO			/* hdrdump flags for "all headers seen?" */
X#define ALLHDRS YES
X
X#define DEFEXP "-"			/* default expiry period */
X
X/* imports from news */
Xextern char *progname;
X
Xextern void fclsexec();				/* from ../libos */
Xextern FILE *fopenexcl();			/* from ../libos */
Xextern char *getcwd();				/* from ../libos */
X
Xextern FILE *fopenclex(), *fopenwclex();	/* from ../libcnews/fopenclex.c */
Xextern char *gethdr();				/* from ../libcnews/gethdr.c */
Xextern char *hostname();			/* from ../libcnews/hostname.c */
Xextern void lockdebug(), newslock(), newsunlock();	/* from ../libcnews/lock.c */
Xextern void errunlock();			/* from ../libcnews/lock.c */
Xextern int ltozan(), ltoza();			/* from ../libcnews/ltoza.c */
Xextern void matchdebug();			/* from ../libcnews/ngmatch.c */
Xextern boolean ngmatch();			/* from ../libcnews/ngmatch.c */
Xextern void mkfilenm(), trim();			/* from ../libcnews/string.c */
Xextern boolean anyhostin(), hostin();		/* from ../libcnews/string.c */
Xextern int hopcount();				/* from ../libcnews/string.c */
Xextern char *skipsp(), *first(), *strsvto();	/* from ../libcnews/string.c */
Xextern char *sendersite(), *nullify();		/* from ../libcnews/string.c */
Xextern char *canonpath();			/* from ../libcnews/string.c */
Xextern void timestamp();			/* from ../libcnews/time.c */
X
Xextern void warning(), error();			/* from ../libc */
Xextern void standard();				/* from ../libc */
Xextern void closeall();				/* from ../libc */
Xextern void stdfdopen();			/* from ../libc */
Xextern int nfclose();				/* from ../libc */
X
X#include "alloc.h"				/* ugh */
!
echo 'hfake/README':
sed 's/^X//' >'hfake/README' <<'!'
XThis is fake header files that C News provides in case your system doesn't
Xhave the corresponding real ones.
!
echo 'hfake/stdlib.h':
sed 's/^X//' >'hfake/stdlib.h' <<'!'
X/* ANSI (draft) definitions */
X
Xtypedef struct {
X	long quot, rem;
X} ldiv_t;
X
Xextern ldiv_t ldiv();
!
echo 'hfake/string.h':
sed 's/^X//' >'hfake/string.h' <<'!'
X/*
X * String functions.
X */
X
Xchar *memcpy(/*char *dst, const char *src, int size*/);
Xchar *memccpy(/*char *dst, const char *src, int ucharstop, int size*/);
Xchar *strcpy(/*char *dst, const char *src*/);
Xchar *strncpy(/*char *dst, const char *src, int size*/);
Xchar *strcat(/*char *dst, const char *src*/);
Xchar *strncat(/*char *dst, const char *src, int size*/);
Xint memcmp(/*const char *s1, const char *s2, int size*/);
Xint strcmp(/*const char *s1, const char *s2*/);
Xint strncmp(/*const char *s1, const char *s2, int size*/);
Xchar *memchr(/*const char *s, int ucharwanted, int size*/);
Xchar *strchr(/*const char *s, int charwanted*/);
Xint strcspn(/*const char *s, const char *reject*/);
Xchar *strpbrk(/*const char *s, const char *breakat*/);
Xchar *strrchr(/*const char *s, int charwanted*/);
Xint strspn(/*const char *s, const char *accept*/);
Xchar *strstr(/*const char *s, const char *wanted*/);
Xchar *strtok(/*char *s, const char *delim*/);
Xchar *memset(/*char *s, int ucharfill, int size*/);
Xint strlen(/*const char *s*/);
X
X/*
X * V7 and Berklix compatibility.
X */
Xchar *index(/*const char *s, int charwanted*/);
Xchar *rindex(/*const char *s, int charwanted*/);
Xint bcopy(/*const char *src, char *dst, int length*/);
Xint bcmp(/*const char *s1, const char *s2, int length*/);
Xint bzero(/*char *dst, int length*/);
X
X/*
X * Putting this in here is really silly, but who am I to argue with X3J11?
X */
Xchar *strerror(/*int errnum*/);
!
echo done


-- 
Please send comp.sources.unix-related mail to rsalz@uunet.uu.net.
Use a domain-based address or give alternate paths, or you may lose out.