DataMuseum.dk

Presents historical artifacts from the history of:

DKUUG/EUUG Conference tapes

This is an automatic "excavation" of a thematic subset of
artifacts from Datamuseum.dk's BitArchive.

See our Wiki for more about DKUUG/EUUG Conference tapes

Excavated with: AutoArchaeologist - Free & Open Source Software.


top - download
Index: ┃ T e

⟦0ac9c76f5⟧ TextFile

    Length: 28095 (0x6dbf)
    Types: TextFile
    Names: »expire.c«

Derivation

└─⟦a0efdde77⟧ Bits:30001252 EUUGD11 Tape, 1987 Spring Conference Helsinki
    └─ ⟦this⟧ »EUUGD11/euug-87hel/sec1/news/src/expire.c« 

TextFile

/*
 * This software is Copyright (c) 1986 by Rick Adams.
 *
 * Permission is hereby granted to copy, reproduce, redistribute or
 * otherwise use this software as long as: there is no monetary
 * profit gained specifically from the use or reproduction or this
 * software, it is not sold, rented, traded or otherwise marketed, and
 * this copyright notice is included prominently in any copy
 * made.
 *
 * The author make no claims as to the fitness or correctness of
 * this software for any use whatsoever, and it is provided as is. 
 * Any use of this software is at the user's own risk.
 *
 * expire - expire daemon runs around and nails all articles that
 *		 have expired.
 */

#ifdef SCCSID
static char	*SccsId = "@(#)expire.c	2.52	3/20/87";
#endif /* SCCSID */

#include "params.h"
#include <errno.h>
#if defined(BSD4_2) || defined(BSD4_1C)
# include <sys/dir.h>
# include <sys/file.h>
#else
# include "ndir.h"
#endif

#ifdef LOCKF
#include <unistd.h>
#endif /* LOCKF */

char *Progname = "expire";	/* used by xerror to identify failing program */

/*	Number of array entries to allocate at a time.	*/
#define SPACE_INCREMENT	1000

struct expdata {
	char *e_name;
	long e_min, e_max;
	time_t	e_droptime, e_expiretime;
	char e_ignorexp;
	char e_doarchive;
	char e_doexpire;
};

extern int	errno;
char	NARTFILE[BUFLEN], OARTFILE[BUFLEN];
char	PAGFILE[BUFLEN], DIRFILE[BUFLEN];
char	NACTIVE[BUFLEN], OACTIVE[BUFLEN];
char	recdate[BUFLEN];
long	rectime, exptime;
extern char *OLDNEWS;
int	verbose = 0;		/* output trace information */
int	ignorexp = 0;		/* ignore Expire: lines */
int	doarchive = 0;		/* archive articles in SPOOL/oldnews */
int	nohistory = 0;		/* ignore history file */
int	dorebuild = 0;		/* rebuild history file */
int	dorbldhistory = 0;	/* rebuild history.d directory */
int	usepost = 0;		/* use posting date to expire */
int	frflag = 0;		/* expire specific user */
int	doupdateactive = 0;	/* update ACTIVE file */
char	baduser[BUFLEN];
extern 	char filename[], nbuf[];

struct timeb Now;

/*
 * This code uses realloc to get more of the multhist array.
 */
struct multhist {
	char	*mh_ident;
	char	*mh_file;
} *multhist;
unsigned int mh_size;
char *calloc();
char *realloc();
struct tm *gmtime();

#ifdef DBM
typedef struct {
	char *dptr;
	int dsize;
} datum;
#else
FILE *nexthistfile();
#endif /* !DBM */

long	expincr;
long	dropincr;
long	atol();
time_t	cgtdate(), time();
FILE *popen();
struct passwd *pw;
struct group *gp;
char	arpat[LBUFLEN];
int	arpatlen = 0;
char	ngpat[LBUFLEN];
int	ngpatlen = 0;
char	afline[BUFLEN];
char	grpsleft[BUFLEN];
struct hbuf h;
int	ExpireLock;
int	rmlock();
time_t	today;

main(argc, argv)
int	argc;
char	**argv;
{
	pathinit();
	(void) umask(N_UMASK);

	/*
	 * Try to run as NEWSUSR/NEWSGRP
	 */
	if ((pw = getpwnam(NEWSUSR)) == NULL)
		xerror("Cannot get NEWSUSR pw entry");

	uid = pw->pw_uid;
	if ((gp = getgrnam(NEWSGRP)) == NULL)
		xerror("Cannot get NEWSGRP gr entry");
	gid = gp->gr_gid;
	(void) setgid(gid);
	(void) setuid(uid);

	if (signal(SIGHUP, SIG_IGN) != SIG_IGN)
		signal(SIGHUP, rmlock);
	if (signal(SIGINT, SIG_IGN) != SIG_IGN)
		signal(SIGINT, rmlock);
	expincr = DFLTEXP;
	dropincr = HISTEXP;
	ngpat[0] = ',';
	arpat[0] = ',';
	while (argc > 1) {
		switch (argv[1][1]) {
		case 'v':
			if (isdigit(argv[1][2]))
				verbose = argv[1][2] - '0';
			else if (argc > 2 && argv[2][0] != '-') {

				argv++;
				argc--;
				verbose = atoi(argv[1]);
			} else
				verbose = 1;
			if (verbose < 3)
				setbuf(stdout, (char *)NULL);
			break;
		case 'e':	/* Use this as default expiration time */
			if (argc > 2 && argv[2][0] != '-') {
				argv++;
				argc--;
				expincr = atol(argv[1]) * DAYS;
			} else if (isdigit(argv[1][2]))
				expincr = atol(&argv[1][2]) * DAYS;
			break;
		case 'E':	/* Use this as default forget time */
			if (argc > 2 && argv[2][0] != '-') {
				argv++;
				argc--;
				dropincr = atol(argv[1]) * DAYS;
			} else if (isdigit(argv[1][2]))
				dropincr = atol(&argv[1][2]) * DAYS;
			break;
		case 'I':	/* Ignore any existing expiration date */
			ignorexp = 2;
			break;
		case 'i':	/* Ignore any existing expiration date */
			ignorexp = 1;
			break;
		case 'n':
			if (argc > 2) {
				argv++;
				argc--;
				while (argc > 1 && argv[1][0] != '-') {
					int argvlen;
					argvlen = strlen(argv[1]);
					if (ngpatlen + argvlen + 2 > sizeof (ngpat)) {
						xerror("Too many groups specified for -n\n");
					}
					if (ngpat[ngpatlen] == '\0') {
						ngpat[ngpatlen++] = ',';
						ngpat[ngpatlen] = '\0';
					}
					strcpy(&ngpat[ngpatlen], argv[1]);
					ngpatlen += argvlen;
					argv++;
					argc--;
				}
				argv--;
				argc++;
			}
			break;
		case 'a':	/* archive expired articles */
			if (access(OLDNEWS,0) < 0){
				perror(OLDNEWS);
				xerror("No archiving possible\n");
			}
			doarchive++;
			if (argc > 2) {
				argv++;
				argc--;
				while (argc > 1 && argv[1][0] != '-') {
					int argvlen;
					argvlen = strlen(argv[1]);
					if (arpatlen + argvlen + 2 > sizeof (arpat)) {
						xerror("Too many groups specified for -a\n");
					}
					if (arpat[arpatlen] == '\0') {
						arpat[arpatlen++] = ',';
						arpat[arpatlen] = '\0';
					}
					strcpy(&arpat[arpatlen], argv[1]);
					arpatlen += argvlen;
					argv++;
					argc--;
				}
				argv--;
				argc++;
			}
			break;
		case 'h':	/* ignore history */
			nohistory++;
			break;
		case 'r':	/* rebuild history file */
			dorebuild++;
			nohistory++;
			break;
		case 'R':	/* just rebuild the dbm files */
#ifdef DBM
			rebuilddbm();
			xxit(0);
#else /* !DBM */
			fprintf(stderr, "You have not compiled expire with DBM, so -R is meaningless\n");
			xxit(1);
#endif /* !DBM */

		case 'p':	/* use posting date to expire */
			usepost++;
			break;
		case 'f':	/* expire messages from baduser */
			frflag++;
			if (argc > 2) {
				strcpy(baduser, argv[2]);
				argv++;
				argc--;
			}
			break;
		case 'u':	/* update the active file from 2.10.1 fmt */
			doupdateactive++;
			break;
		case 'H':	/* convert to history.d format */
			dorbldhistory++;
			break;
		default:
			printf("Usage: expire [ -v [level] ] [-e days ] [-i] [-a] [-r] [-h] [-p] [-u] [-f username] [-n newsgroups] [-H]\n");
			xxit(1);
		}
		argc--;
		argv++;
	}
	if (dorbldhistory) {
#ifndef DBM
		rebuildhistorydir();
#endif /* !DBM */
		exit(0);
	}
	if (dropincr < expincr) {
		dropincr = HISTEXP;
		fprintf(stderr, "History expiration time < article expiration time. Default used.\n");
	}
	if (ngpat[0] == ',')
		(void) strcpy(ngpat, "all,");
	if (arpat[0] == ',')
		(void) strcpy(arpat, "all,");
	(void) ftime(&Now);
	today = Now.time;
	if (chdir(SPOOL))
		xerror("Cannot chdir %s", SPOOL);

	if (verbose) {
		printf("expire: nohistory %d, rebuild %d, doarchive %d\n",
			nohistory, dorebuild, doarchive);
		printf("newsgroups: %s\n",ngpat);
		if (doarchive)
			printf("archiving: %s\n",arpat);
	}

#ifdef DBM
	(void) sprintf(OARTFILE, "%s/%s", LIB, "ohistory");
#endif /* DBM */
	(void) sprintf(NARTFILE, "%s/%s", LIB, "nhistory");

	(void) sprintf(OACTIVE, "%s/%s", LIB, "oactive");
	(void) sprintf(NACTIVE, "%s/%s", LIB, "nactive");

	if (!doupdateactive) {
		expire();
#ifndef DBM
		rebuildhistorydir();
#endif
	}

	updateactive();
	rmlock();

	/*
	 * Now read in any saved news.
	 */
#ifdef PROFILING
	monitor((int(*)())0,(int(*)())0,0,0,0);
#endif /* PROFILING */
#ifdef IHCC
	/*afline happens to be available - (we're getting out anyway)*/
	sprintf(afline, "%s/%s", logdir(HOME), RNEWS);
	execl(afline, "rnews", "-U", (char *)NULL);
#else /* ! IHCC */
	execl(RNEWS, "rnews", "-U", (char *)NULL);
#endif /* ! IHCC */
	perror(RNEWS);
	xxit(1);
	/* NOTREACHED */
}

expire()
{
	register char	*p1, *p2, *p3;
	register time_t newtime;
	register FILE *fp = NULL;
	FILE	*ohfd, *nhfd;
	int i;
	char	fn[BUFLEN];
	DIR	*ngdirp = NULL;
	static struct direct *ngdir;

#ifdef DBM
	if (!dorebuild) {
		(void) sprintf(PAGFILE, "%s/%s", LIB, "nhistory.pag");
		(void) sprintf(DIRFILE, "%s/%s", LIB, "nhistory.dir");
		(void) close(creat(PAGFILE, 0666));
		(void) close(creat(DIRFILE, 0666));
		initdbm(NARTFILE);
	}
#endif

	if (nohistory) {
		ohfd = xfopen(ACTIVE, "r");
		if (dorebuild) {
			/* Allocate initial space for multiple newsgroup (for
			   an article) array */
			multhist = (struct multhist *)calloc (SPACE_INCREMENT,
					sizeof (struct multhist));
			mh_size = SPACE_INCREMENT;

			(void) sprintf(afline, "exec sort -t\t +1.6 -2 +1 >%s", NARTFILE);
			if ((nhfd = popen(afline, "w")) == NULL)
				xerror("Cannot exec %s", afline);
		} else
			nhfd = xfopen("/dev/null", "w");
	} else {
#ifdef DBM
		ohfd = xfopen(ARTFILE, "r");
#else
		ohfd = nexthistfile((FILE *)NULL);
#endif /* DBM */
		nhfd = xfopen(NARTFILE, "w");
	}

	dolock();

	for(i=0;i<NUNREC;i++)
		h.unrec[i] = NULL;

	while (TRUE) {
		fp = NULL;
		if (nohistory) {
			recdate[0] = '\0';
			do {
				if (ngdir == NULL) {
					if ( ngdirp != NULL )
						closedir(ngdirp);
					if (fgets(afline, BUFLEN, ohfd) == NULL)
						goto out;
					(void) strcpy(nbuf, afline);
					p1 = index(nbuf, ' ');
					if (p1 == NULL)
						p1 = index(nbuf, '\n');
					if (p1 != NULL)
						*p1 = NULL;
					if (!ngmatch(nbuf, ngpat))
						continue;

					/* Change a group name from
					   a.b.c to a/b/c */
					for (p1=nbuf; *p1; p1++)
						if (*p1 == '.')
							*p1 = '/';

					if ((ngdirp = opendir(nbuf)) == NULL)
						continue;

				}
				ngdir = readdir(ngdirp);
			/*	Continue looking if not an article.	*/
			} while (ngdir == NULL || !islegal(fn,nbuf,ngdir->d_name));

			p2 = fn;
			if (verbose > 2)
				printf("article: %s\n", fn);
			strcpy(filename, dirname(fn));
			fp = access(filename, 04) ? NULL : art_open(filename, "r");
		} else {
			char dc;
#ifdef DBM
			if (fgets(afline, BUFLEN, ohfd) == NULL)
				break;
#else
			if (fgets(afline, BUFLEN, ohfd) == NULL)
				if (!(ohfd = nexthistfile(ohfd)))
					break;
				else
					continue;
#endif /* DBM */
			if (verbose > 2)
				printf("article: %s", afline);
			p1 = index(afline, '\t');
			if (!p1)
				continue;
			*p1 = '\0';
			(void) strcpy(h.ident, afline);
			*p1 = '\t';
			p2 = index(p1 + 1, '\t');
			if (!p2)
				continue;
			*p2 = '\0';
			(void) strcpy(recdate, p1+1);
			rectime = cgtdate(recdate);
			*p2++ = '\t';
			(void) strcpy(nbuf, p2);
			p3 = index(nbuf, '/');
			if (p3) {
				register char *p4;

				p4 = index(p3, '\n');
				if (p4) {
					while (p4[-1] == ' ')
						p4--;
					*p4 = '\0';
				}

				/*
				 * convert list of newsgroups from
				 *	ng1/num ng2/num ...
				 * to
				 *	ng1,ng2,...
				 */
				p4 = p3;
				do {
					*p3++ = NGDELIM;
					while (*p4 != '\0' && *p4 != ' ')
						p4++;
					if (*p4++ == '\0') {
						*--p3 = '\0';
						break;
					}
					while (*p3 = *p4++) {
						if (*p3 == '/')
							break;
						else
							p3++;
					}
				} while (*p3);
			} else {
				/*
				 * Nothing after the 2nd tab.  This happens
				 * when there is no message left in the spool
				 * directory, only the memory of it in the
				 * history file. (That is, it got cancelled
				 * or expired.) Use date in the history file
				 * to decide if we should keep the memory.
				 */
				grpsleft[0] = '\0';
				goto checkdate;
			}
			if (!ngmatch(nbuf, ngpat) ||
			     ((rectime+expincr > today) && !dorebuild &&
				 !frflag && !usepost && recdate[0] != ' '))
				goto keephist;
			if (!dorebuild && !frflag && !usepost &&
				recdate[0] != ' ') {
				grpsleft[0] = '\0';
				goto nailit; /* just expire it */
			}

			/*
			 * Look for the file--possibly several times,
			 * if it was posted to several news groups.
			 */
			dc = ' ';
			p3 = p2;
			while (dc != '\n') {
				p1 = index(p3, ' ');
				if (p1) {
					dc = ' ';
					*p1 = '\0';
				} else {
					p1 = index(p3, '\n');
					if (p1 && p1 > p3) {
						dc = '\n';
						*p1 = '\0';
					} else {
						fp = NULL;
						break;
					}
				}
				strcpy(filename, dirname(p3));
				if (access(filename, 4) == 0 &&
					((fp=art_open(filename, "r")) != NULL))
						break;
				p3 = p1 + 1;
			}
			if (p1)
				*p1 = dc;
		}

		if (fp == NULL) {
			/*
			 * this probably means that the article has been
			 * cancelled.  Lets assume that, and make an
			 * entry in the history file to that effect.
			 */
			if (verbose)
				perror(filename);
			strcpy(p2, "cancelled\n");
			grpsleft[0] = '\0';
			goto checkdate;
		}
		for(i=0; i<NUNREC; i++)
			if (h.unrec[i] != NULL) {
				free(h.unrec[i]);
				h.unrec[i] = NULL;
			} else
				break;
		if (!hread(&h, fp, TRUE)) {
			printf("Garbled article %s.\n", filename);
			(void) fclose(fp);
			/*
			 * Usually means disk ran out of space.
			 * Drop this article from our history file
			 * completely, so we have a chance of picking
			 * it up again from another feed ..
			 */
			goto nailit;
		}
		if (nohistory) {
			if (recdate[0] == '\0') {
				struct stat statb;
				if (fstat(fileno(fp), &statb) < 0)
					rectime = cgtdate(h.subdate);
				else
					rectime = statb.st_mtime;
			} else
				rectime = cgtdate(recdate);
		}
		if (dorebuild) {
			register char	*cp, *lastslash;
			register struct multhist *mhp;

			/*
			 * Format of filename until now was /SPOOL/a/b/c/4
			 * and this code changes it to a.b.c/4 (the correct
			 * kind of entry in the history file.)
			 *
			 * This cannot be a strcpy because the addresses
			 * overlap and some machines cannot handle that.
			 */
			p1 = filename;
			cp = p1 + strlen(SPOOL);
			while (*++cp) {
				if (*cp == '/') {
					lastslash = p1;
					*p1++ = '.';
				} else
					*p1++ = *cp;
			}
			*p1 = '\0';
			*lastslash = '/';

			if ((cp = index(h.nbuf, NGDELIM)) == NULL) {
				struct tm *tm;
saveit:
				tm = gmtime(&rectime);
				if (fprintf(nhfd,
#ifdef USG
				     "%s\t%s%2.2d/%2.2d/%d %2.2d:%2.2d\t%s\n",
#else /* !USG */
				     "%s\t%s%02d/%02d/%d %02d:%02d\t%s\n",
#endif /* !USG */
					h.ident, h.expdate[0] ? " " : "",
					tm->tm_mon+1, tm->tm_mday, tm->tm_year,
					tm->tm_hour, tm->tm_min, filename)
					== EOF)
						xerror("History write failed");
				(void) fclose(fp);
				continue;
			}
			for (mhp = multhist; mhp < multhist+mh_size && mhp->mh_ident != NULL; mhp++) {
				if (mhp->mh_file == NULL)
					continue;
				if (strcmp(mhp->mh_ident, h.ident))
					continue;
				(void) strcat(filename, " ");
				(void) strcat(filename, mhp->mh_file);
				free(mhp->mh_file);
				mhp->mh_file = NULL;
				/*
				 * if we have all the links, write to hist now
				 */
				if (chrcnt(filename, ' ') == chrcnt(cp,NGDELIM))
					goto saveit;
				break;
			}

			/*
			 * Here is where we realloc the multhist space rather
			 * than the old way of static allocation.  It is
			 * really trivial.  We just clear out the space
			 * in case it was reused.  The old static array was
			 * guaranteed to be cleared since it was cleared when
			 * the process started.
			 */
			if (mhp >= multhist + mh_size) {
				multhist = (struct multhist *)
					realloc ((char *)multhist,
					  sizeof (struct multhist) *
					  (SPACE_INCREMENT + mh_size));
				if (multhist == NULL)
					xerror("Too many articles with multiple newsgroups");
				for (mhp = multhist + mh_size;
				  mhp < multhist+mh_size+SPACE_INCREMENT;
					mhp++) {
					mhp->mh_ident = NULL;
					mhp->mh_file = NULL;
				}
				mhp = multhist + mh_size;
				mh_size += SPACE_INCREMENT;
			}

			if (mhp->mh_ident == NULL) {
				mhp->mh_ident = malloc(strlen(h.ident)+1);
				(void) strcpy(mhp->mh_ident, h.ident);
			}
			cp = malloc(strlen(filename) + 1);
			if (cp == NULL)
				xerror("Out of memory");
			(void) strcpy(cp, filename);
			mhp->mh_file = cp;
			(void) fclose(fp);
			continue;
		}

		(void) fclose(fp);

		if (h.expdate[0]) {
			Now.time = rectime;
			exptime = cgtdate(h.expdate);
		}
		newtime = (usepost ? cgtdate(h.subdate) : rectime) + expincr;
		if (!h.expdate[0] || ignorexp == 2 ||
		    (ignorexp == 1 && newtime < exptime))
			exptime = newtime;
		if (frflag ? strcmp(baduser,h.from)==0 : today >= exptime) {
nailit:
#ifdef DEBUG
			printf("cancel %s\n", filename);
#else /* !DEBUG */
			if (verbose)
				printf("cancel %s\n", h.ident);
			ulall(p2, &h);
			(void) sprintf(p2, "%s\n", grpsleft);
			if (verbose > 2 && grpsleft[0])
				printf("Some good in %s\n", h.ident);
#endif /* !DEBUG */
		} else {
			if (verbose > 2)
				printf("Good article %s\n", h.ident);
			grpsleft[0] = '!';
		}

checkdate:
		if (grpsleft[0] == '\0' && today >= rectime + dropincr) {
			if (verbose > 3)
				printf("Drop history of %s - %s\n",
				    h.ident, recdate);
		} else {
#ifdef DBM
			long hpos;
#endif /* DBM */
keephist:
#ifdef DBM
			hpos = ftell(nhfd);
#endif /* DBM */

			if (verbose > 3)
				printf("Retain history of %s - %s\n",
				    h.ident, recdate);
			if (fputs(afline, nhfd) == EOF)
				xerror("history write failed");
#ifdef DBM
			if (!dorebuild)
				remember(h.ident, hpos);
#endif /* DBM */
		}
	}
out:
	if (dorebuild) {
		register struct multhist *mhp;
		struct tm *tm;
		for (mhp = multhist; mhp < multhist+mh_size && mhp->mh_ident != NULL; mhp++)
			if (mhp->mh_file != NULL) {
				if (verbose)
					printf("Article: %s [%s] Cannot find all links\n", mhp->mh_ident, mhp->mh_file);
				(void) sprintf(filename,"%s/%s",SPOOL,mhp->mh_file);
				for (p1 = filename; *p1 != ' ' && *p1 != '\0'; p1++)
					if (*p1 == '.')
						*p1 = '/';
				*p1 = '\0';
				if ((fp = art_open(filename, "r")) == NULL) {
					if (verbose)
						printf("Can't open %s.\n", filename);
					continue;
				}
				if (!hread(&h, fp, TRUE)) {
					printf("Garbled article %s.\n", filename);
					(void) fclose(fp);
					continue;
				} else {
					struct stat statb;
					if (fstat(fileno(fp), &statb) < 0)
						rectime = cgtdate(h.subdate);
					else
						rectime = statb.st_mtime;
				}
				tm = gmtime(&rectime);
				if ( fprintf(nhfd,
#ifdef USG
					"%s\t%s%2.2d/%2.2d/%d %2.2d:%2.2d\t%s\n",
#else /* !USG */
					"%s\t%s%02d/%02d/%d %02d:%02d\t%s\n",
#endif /* !USG */
					h.ident, h.expdate[0] ? " " : "",
					tm->tm_mon+1, tm->tm_mday, tm->tm_year,
					tm->tm_hour, tm->tm_min, mhp->mh_file)
					== EOF )
						xerror("History write failed");
				(void) fclose(fp);
				continue;
			}
		(void) pclose(nhfd);
		free ((char *)multhist);
	} else
		if (fclose(nhfd))
			xerror("History write failed, %s", errmsg(errno));

	if (dorebuild || !nohistory) {
#ifndef DBM
		(void) rename(ARTFILE, OARTFILE);
#endif /* !DBM */
		(void) rename(NARTFILE, ARTFILE);
#ifdef DBM
		if (dorebuild)
			rebuilddbm( );
		else {
			char tempname[BUFLEN];
			(void) sprintf(tempname,"%s.pag", ARTFILE);
			(void) strcat(NARTFILE, ".pag");
			(void) rename(NARTFILE, tempname);
			(void) sprintf(tempname,"%s.dir", ARTFILE);
			(void) strcpy(rindex(NARTFILE, '.'), ".dir");
			(void) rename(NARTFILE, tempname);
		}
#endif
	}
}

#if defined(BSD4_2) || defined(LOCKF)
static int LockFd = -1;
#endif

dolock()
{
	/* set up exclusive locking so inews does not run while expire does */
#if defined(BSD4_2) || defined(LOCKF)
	LockFd = open(ACTIVE, 2);
# ifdef	LOCKF
	if (lockf(LockFd, F_LOCK, 0) < 0)
# else	/* BSD4_2 */
	if (flock(LockFd, LOCK_EX) < 0)
# endif	/* BSD4_2 */
		xerror("Can't get lock for expire: %s", errmsg(errno));
#else	/* !BSD4_2 && !LOCKF */
	int i = 0;
	sprintf(afline,"%s.lock", ACTIVE);
	while (LINK(ACTIVE, afline) < 0 && errno == EEXIST) {
		if (i++ > 5)
			xerror("Can't get lock for expire");
		sleep(i*2);
	}
#endif	/* !BSD4_2  && !LOCKF */
}

rmlock()
{
#if defined(BSD4_2) || defined(LOCKF)
	close(LockFd);
#else
	sprintf(bfr, "%s.lock", ACTIVE);
	(void) UNLINK(bfr);
#endif	/* !BSD4_2 */
}

updateactive()
{
	register char	*p1;
	FILE	*ohfd, *nhfd;
	DIR	*ngdirp = NULL;
	static struct direct *ngdir;

	if (verbose)
		printf("updating active file %s\n", ACTIVE);
	ohfd = xfopen(ACTIVE, "r");
	nhfd = xfopen(NACTIVE, "w");
	do {
		long n;
		long maxart, minart;
		char cansub;
		int gdsize, hassubs;
		struct stat stbuf;

		if (fgets(afline, BUFLEN, ohfd) == NULL)
			continue;
		if (sscanf(afline,"%s %ld %ld %c",nbuf,&maxart, &minart,
		    &cansub) < 4)
			xerror("Active file corrupt");
		if (verbose > 3)
			printf("looking at group %s\n", nbuf);
		if (!ngmatch(nbuf, ngpat)) {
			if (fputs(afline, nhfd) == EOF)
				xerror("active file write failed");
			continue;
		}
		minart = 99999L;
		/* Change a group name from a.b.c to a/b/c */
		for (p1=nbuf; *p1; p1++)
			if (*p1 == '.')
				*p1 = '/';

		hassubs = stat(nbuf, &stbuf) != 0 || stbuf.st_nlink != 2;
		gdsize = strlen(nbuf);
		if ((ngdirp = opendir(nbuf)) != NULL) {
			while (ngdir = readdir(ngdirp)) {
				nbuf[gdsize] = '/';
				(void) strcpy(&nbuf[gdsize+1], ngdir->d_name);
				/* We have to do a stat because of micro.6809 */
				if (hassubs && (stat(nbuf, &stbuf) < 0 ||
					!(stbuf.st_mode&S_IFREG)) )
					continue;
				n = atol(ngdir->d_name);
				if (n > 0 && n < minart)
					minart = n;
				if (n > 0 && n > maxart)
					maxart = n;
			}
			closedir(ngdirp);
		}
		afline[gdsize] = '\0';
		if (minart > maxart)
			minart = maxart;
#ifdef USG
		if (verbose > 4)
			printf("\tmaxart = %5.5ld, minart = %5.5ld\n",
				maxart, minart);
		if (fprintf(nhfd,"%s %5.5ld %5.5ld %c\n", afline, maxart,
			minart, cansub) == EOF)
			xerror("Active file write failed");
#else
		if (verbose > 4)
			printf("\tmaxart = %05ld, minart = %05ld\n",
				maxart, minart);
		if (fprintf(nhfd,"%s %05ld %05ld %c\n", afline, maxart,
			minart, cansub) == EOF)
			xerror("Active file write failed");
#endif /* !USG */
	} while (!feof(ohfd));
	if (fclose(nhfd))
		xerror("Active file write failed, %s", errmsg(errno));
	(void) fclose(ohfd); /* this might unlock inews as a side effect */

	(void) rename(ACTIVE, OACTIVE);
	(void) rename(NACTIVE, ACTIVE);
}

/* Unlink (using unwound tail recursion) all the articles in 'artlist'. */
ulall(artlist, hp)
char	*artlist;
struct hbuf *hp;
{
	register char	*p, *q;
	int	last = 0;
	char	newname[BUFLEN];
	time_t	timep[2];
	char *fn;

	grpsleft[0] = '\0';
	do {
		if (verbose > 2)
			printf("ulall '%s', '%s'\n", artlist, hp->subdate);
		if (nohistory) {
			last = 1;
		} else {
			while (*artlist == ' ' || *artlist == '\n' || *artlist == ',')
				artlist++;
			if (*artlist == '\0')
				return;
			p = index(artlist, ' ');
			if (p == NULL) {
				last = 1;
				p = index(artlist, '\n');
			}
			if (p == NULL) {
				last = 1;
				fn = dirname(artlist);
				if (UNLINK(fn) < 0 && errno != ENOENT)
					perror(fn);
				return;
			}
			if (p)
				*p = 0;
		}
		strcpy(newname, artlist);
		q = index(newname,'/');
		if (q) {
			*q++ = NGDELIM;
			*q = '\0';
		} else {
			q = index(newname, '\0');
			if (q == artlist)		/* null -> the end */
				return;
			/* should be impossible to get here */
		}
		fn = dirname(artlist);
		if (ngmatch(newname, ngpat)) {
			if (doarchive){
				if (ngmatch(newname, arpat)) {
					q = fn + strlen(SPOOL) + 1;
					(void) sprintf(newname, "%s/%s", OLDNEWS, q);
					if (verbose)
						printf("link %s to %s\n", fn, newname);
					if (LINK(fn, newname) == -1) {
						if (mkparents(newname) == 0)
							if (LINK(fn, newname) == -1)
								fcopy(fn, newname);
					}
					timep[0] = timep[1] = cgtdate(hp->subdate);
					(void) utime(newname, timep);
				}
			}
			if (verbose)
				printf("unlink %s\n", fn);
			if (UNLINK(fn) < 0 && errno != ENOENT)
				perror(fn);
		} else {
			if (verbose > 3)
				printf("retain %s (%s)\n", hp->ident, fn);
			strcat(grpsleft, artlist);
			strcat(grpsleft, " ");
		}
		artlist = p + 1;
	} while (!last);
}

fcopy(fn, newname)
char *fn, *newname;
{
	int f1, f2;
	int r;
	char buf[BUFSIZ];
	f1 = open(fn, 0);
	if (f1 < 0)
		return -1;
	f2 = open(newname, 1);
	if (f2 < 0) {
		if (errno == ENOENT) {
			f2 = creat(newname,0644);
			if (f2 < 0) {
				close(f1);
				return -1;
			}
		} else {
			close(f1);
			return -1;
		}
	}
	while((r=read(f1, buf, BUFSIZ)) > 0)
		write(f2, buf, r);
	(void) close(f1);
	(void) close(f2);
	return 0;
}

/*
 * Count instances of c in s
 */
chrcnt(s, c)
register char *s;
register c;
{
	register n = 0;
	register cc;

	while (cc = *s++)
		if (cc == c)
			n++;
	return n;
}

/*
 * If any parent directories of this dir don't exist, create them.
 */
mkparents(fullname)
char *fullname;
{
	char buf[200];
	register char *p;
	int rc;

	(void) strcpy(buf, fullname);
	p = rindex(buf, '/');
	if (p)
		*p = '\0';
	if (access(buf, 0) == 0)
		return 0;
	mkparents(buf);
	if ((rc = mkdir(buf, 0755)) < 0)
		perror("mkdir failed");
	if (verbose)
		printf("mkdir %s, rc %d\n", buf, rc);

	return rc;
}

/*	Make sure this file is a legal article. */
islegal(fullname, path, name)
register char *fullname;
register char *path;
register char *name;
{
	struct stat buffer;

	(void) sprintf(fullname, "%s/%s", path, name);

	/* make sure the article is numeric. */
	while (*name != '\0')
		if (!isascii(*name) || !isdigit(*name))
			return 0;
		else
			name++;

	/*  Now make sure we don't have a group like net.micro.432,
	 *  which is numeric but not a regular file -- i.e., check
	 *  for being a regular file.
	 */
	if ((stat(fullname, &buffer) == 0) &&
		((buffer.st_mode & S_IFMT) == S_IFREG)) {
		/* Now that we found a legal group in a/b/c/4
		   notation, switch it to a.b.c/4 notation.  */
		for (name = fullname; name != NULL && *name != '\0'; name++)
			if (*name == '/' && name != rindex (name, '/'))
				*name = '.';

			return 1;
	}
	return 0;
}

#ifdef DBM
/*
 * This is taken mostly intact from ../cvt/cvt.hist.c and is used at the
 * end by the options that make a new history file.
 * Routine to convert history file to dbm file.  The old 3 field
 * history file is still kept there, because we need it for expire
 * and for a human readable copy.  But we keep a dbm hashed copy
 * around by message ID so we can answer the yes/no question "have
 * we already seen this message".  The content is the ftell offset
 * into the real history file when we get the article - you can't
 * really do much with this because the file gets compacted.
 */

FILE *fd;

char namebuf[BUFSIZ];
char lb[BUFSIZ];

rebuilddbm()
{
	register char *p;
	long fpos;

	(void) sprintf(namebuf, "%s.dir", ARTFILE);
	(void) close(creat(namebuf, 0666));
	(void) sprintf(namebuf, "%s.pag", ARTFILE);
	(void) close(creat(namebuf, 0666));
	(void) sprintf(namebuf, "%s", ARTFILE);

	fd = fopen(namebuf, "r");
	if (fd == NULL) {
		perror(namebuf);
		xxit(2);
	}

	initdbm(namebuf);
	while (fpos=ftell(fd), fgets(lb, BUFSIZ, fd) != NULL) {
		p = index(lb, '\t');
		if (p)
			*p = 0;
		remember(lb, fpos);
	}
}

remember(article, fileoff)
register char *article;
long fileoff;
{
	datum	lhs, rhs;

	lcase(article);
	lhs.dptr = article;
	lhs.dsize = strlen(article) + 1;
	rhs.dptr = (char *) &fileoff;
	rhs.dsize = sizeof fileoff;

	if (verbose > 5)
		printf("remember: %s @ %ld\n", article, fileoff);
	if (store(lhs, rhs) < 0)
		xerror("dbm store failed");
}
#else
/*
 * Open the next history subdirectory file
 */

FILE *nexthistfile(ofp)
FILE *ofp;
{
	static int histfilecounter = -1;

	if (ofp)
		fclose(ofp);
	do {
		if (++histfilecounter > 9)
			return NULL;
		sprintf(bfr, "%s.d/%d", ARTFILE, histfilecounter);
		if (verbose > 3)
			printf("reading history file %s\n", bfr);
		ofp = xfopen(bfr, "r");
	} while (ofp == NULL);
	return ofp;
}

/*
 * Rebuild the history subdirectory from LIBDIR/history
 */
rebuildhistorydir()
{
	char fn[BUFLEN], ofn[BUFLEN];
	register int i;
	FILE *subfd[10], *ohfd;

	/* rebuild history subfiles */
	(void) sprintf(fn, "%s.od", ARTFILE);
	if (access(fn,0) != 0)
		(void) mkdir(fn, 0755);
	(void) sprintf(fn, "%s.d", ARTFILE);
	if (verbose)
		printf("Rebuilding history subfile directory %s.\n", fn);
	if (access(fn,0) != 0)
		(void) mkdir(fn, 0755);
	for (i = 0; i < 10; i++) {
		(void) sprintf(fn, "%s.d/%c", ARTFILE, i + '0');
		(void) sprintf(ofn, "%s.od/%c", ARTFILE, i + '0');
		(void) rename(fn, ofn);
		close(creat(fn, 0644));
		subfd[i] = xfopen(fn, "w+");
	}
	ohfd = xfopen(ARTFILE, "r");
	while (fgets(fn, BUFLEN, ohfd) != NULL) {
		i = findhfdigit(fn) - '0';
		fputs(fn, subfd[i]);
	}
	(void) fclose(ohfd);
	for (i = 0; i < 10; i++)
		if (ferror(subfd[i]) || fclose(subfd[i]))
			xerror("History subfile write");
	(void) UNLINK(ARTFILE);
}
#endif /* !DBM */

xxit(i)
{
	rmlock();
	exit(i);
}