4.4BSD/usr/src/contrib/news/inn/expire/expireover.c
/* $Revision: 1.3 $
**
** Expire overview database.
*/
#include "configdata.h"
#include <stdio.h>
#include <ctype.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/uio.h>
#include <fcntl.h>
#include <errno.h>
#include "clibrary.h"
#include "qio.h"
#include "mydir.h"
#include "libinn.h"
#include "macros.h"
#include "paths.h"
#define START_LIST_SIZE 128
/*
** Information about a line in the overview file.
*/
typedef struct _LINE {
ARTNUM Article;
char *Start;
int Length;
int Offset;
} LINE;
/*
** A list of articles; re-uses space.
*/
typedef struct _LIST {
int Used;
int Size;
ARTNUM *Articles;
} LIST;
/*
** A buffer; re-uses space.
*/
typedef struct _BUFFER {
int Used;
int Size;
char *Data;
} BUFFER;
/*
** Information about the schema of the news overview files.
*/
typedef struct _ARTOVERFIELD {
char *Header;
int Length;
BOOL HasHeader;
} ARTOVERFIELD;
/*
** Append an article to an LIST.
*/
#define LISTappend(L, a) \
if ((L).Size == (L).Used) { \
(L).Size *= 2; \
RENEW((L).Articles, ARTNUM, (L).Size); \
(L).Articles[(L).Used++] = (a); \
} \
else \
(L).Articles[(L).Used++] = (a)
/*
** Global variables.
*/
STATIC char SPOOL[] = _PATH_SPOOL;
STATIC char *SCHEMA = _PATH_SCHEMA;
STATIC BOOL InSpoolDir;
STATIC BOOL Verbose;
STATIC BOOL DoNothing;
STATIC ARTOVERFIELD *ARTfields;
STATIC int ARTfieldsize;
/*
** Sorting predicate for qsort to put articles in numeric order.
*/
STATIC int
LISTcompare(p1, p2)
POINTER *p1;
POINTER *p2;
{
ARTNUM *ip1;
ARTNUM *ip2;
ip1 = CAST(ARTNUM*, p1);
ip2 = CAST(ARTNUM*, p2);
return *ip1 - *ip2;
}
/*
** If list is big enough, and out of order, sort it.
*/
STATIC void
LISTsort(lp)
LIST *lp;
{
register int i;
register ARTNUM *ap;
for (ap = lp->Articles, i = lp->Used - 1; --i >= 0; ap++)
if (ap[0] >= ap[1]) {
qsort((POINTER)lp->Articles, (SIZE_T)lp->Used,
sizeof lp->Articles[0], LISTcompare);
break;
}
}
/*
** Unlock the group.
*/
STATIC void
UnlockGroup(lfd, lockfile)
int lfd;
char *lockfile;
{
if (lfd > 0) {
if (unlink(lockfile) < 0 && errno != ENOENT)
(void)fprintf(stderr, "expireover cant unlink %s %s\n",
lockfile, strerror(errno));
if (close(lfd) < 0)
(void)fprintf(stderr, "expireover cant close %s %s\n",
lockfile, strerror(errno));
lfd = -1;
}
}
/*
** Sorting predicate to put lines in numeric order.
*/
STATIC int
LINEcompare(p1, p2)
POINTER *p1;
POINTER *p2;
{
LINE *lp1;
LINE *lp2;
lp1 = CAST(LINE*, p1);
lp2 = CAST(LINE*, p2);
return lp1->Article - lp2->Article;
}
/*
** Take in a sorted list of count article numbers in group, and delete
** them from the overview file.
*/
STATIC void
RemoveLines(group, Deletes)
char *group;
LIST *Deletes;
{
static BUFFER B;
static LINE *Lines;
static int LineSize;
register struct iovec *vp;
register LINE *lp;
register LINE *end;
register char *p;
register char *next;
register ARTNUM *ap;
register int i;
struct stat Sb;
struct iovec iov[8];
char file[SPOOLNAMEBUFF];
char lockfile[SPOOLNAMEBUFF];
int fd;
int count;
int lfd;
if (Verbose) {
for (ap = Deletes->Articles, i = Deletes->Used; --i >= 0; ap++)
(void)printf("- %s/%ld\n", group, *ap);
if (DoNothing)
return;
}
/* Lock the group. */
(void)sprintf(lockfile, "%s/.LCK%s", group, _PATH_OVERVIEW);
lfd = open(lockfile, O_WRONLY | O_TRUNC | O_CREAT, ARTFILE_MODE);
if (lfd < 0) {
(void)fprintf(stderr, "Can't open %s, %s\n", lockfile, strerror(errno));
return;
}
/* Open file, lock it. */
(void)sprintf(file, "%s/%s", group, _PATH_OVERVIEW);
for ( ; ; ) {
if ((fd = open(file, O_RDWR)) < 0) {
(void)fprintf(stderr, "Can't open %s, %s\n", file, strerror(errno));
UnlockGroup(lfd, lockfile);
return;
}
if (LockFile(fd, FALSE) >= 0)
break;
/* Wait for lock; close file -- might be unlinked -- and try again. */
(void)LockFile(fd, TRUE);
(void)close(fd);
}
if (fstat(fd, &Sb) < 0) {
(void)fprintf(stderr, "Can't open %s, %s\n", file, strerror(errno));
UnlockGroup(lfd, lockfile);
(void)close(fd);
return;
}
if (Sb.st_size == 0) {
/* Empty file; done deleting. */
UnlockGroup(lfd, lockfile);
(void)close(fd);
return;
}
/* Read in the whole file. */
if (B.Size == 0) {
B.Size = Sb.st_size + 1;
B.Data = NEW(char, B.Size);
}
else if (B.Size < Sb.st_size) {
B.Size = Sb.st_size + 1;
RENEW(B.Data, char, B.Size);
}
if (xread(fd, B.Data, Sb.st_size) < 0) {
(void)fprintf(stderr, "Can't read %s, %s\n", file, strerror(errno));
UnlockGroup(lfd, lockfile);
(void)close(fd);
return;
}
B.Data[Sb.st_size] = '\0';
/* Count lines, get space. */
for (i = 1, p = B.Data; (p = strchr(p, '\n')) != NULL && *++p; i++)
continue;
if (LineSize == 0) {
LineSize = i;
Lines = NEW(LINE, LineSize + 1);
}
else if (LineSize < i) {
LineSize = i;
RENEW(Lines, LINE, LineSize + 1);
}
/* Build line array. */
for (lp = Lines, p = B.Data; ; p = next, lp++) {
if ((next = strchr(p, '\n')) == NULL)
break;
lp->Start = p;
lp->Length = ++next - p;
lp->Article = atol(p);
}
qsort((POINTER)Lines, (SIZE_T)(lp - Lines), sizeof lp[0], LINEcompare);
/* Remove duplicates. */
for (end = lp - 1, lp = Lines; lp < end; lp++)
if (lp[0].Article == lp[1].Article)
lp->Article = 0;
/* Scan through lines, collecting clumps and skipping holes. */
ap = Deletes->Articles;
count = Deletes->Used;
iov[0].iov_len = 0;
for (vp = iov, lp = Lines; lp < end + 1; lp++) {
/* An already-removed article, or one that should be? */
if (lp->Article == 0)
continue;
/* Skip delete items before the current one. */
while (*ap < lp->Article && count > 0) {
ap++;
count--;
}
if (count > 0 && lp->Article == *ap) {
while (*ap == lp->Article && count > 0) {
ap++;
count--;
}
continue;
}
/* We're keeping this entry; see if we can add it to any
* in-progress iov element. */
if (vp->iov_len) {
if (vp->iov_base + vp->iov_len == lp->Start) {
/* Contiguous. */
vp->iov_len += lp->Length;
continue;
}
/* Doesn't fit -- get a new element. */
if (++vp == ENDOF(iov)) {
if (xwritev(lfd, iov, SIZEOF(iov)) < 0) {
(void)fprintf(stderr, "Can't write %s, %s\n",
lockfile, strerror(errno));
UnlockGroup(lfd, lockfile);
(void)close(fd);
return;
}
vp = iov;
}
}
/* Start new element. */
vp->iov_base = lp->Start;
vp->iov_len = lp->Length;
}
/* Write out remaining. */
if (vp->iov_len)
vp++;
if (iov[0].iov_len && xwritev(lfd, iov, vp - iov) < 0) {
(void)fprintf(stderr, "Can't write %s, %s\n",
lockfile, strerror(errno));
UnlockGroup(lfd, lockfile);
(void)close(fd);
return;
}
if (rename(lockfile, file) < 0)
(void)fprintf(stderr, "Can't rename %s, %s\n",
lockfile, strerror(errno));
/* Don't call UnlockGroup; do it inline. */
if (close(lfd) < 0)
(void)fprintf(stderr, "expireover cant close %s %s\n",
file, strerror(errno));
if (close(fd) < 0)
(void)fprintf(stderr, "expireover cant close unlinked %s %s\n",
file, strerror(errno));
}
/*
** Read the overview schema.
*/
void
ARTreadschema()
{
register FILE *F;
register char *p;
register ARTOVERFIELD *fp;
register int i;
char buff[SMBUF];
/* Open file, count lines. */
if ((F = fopen(SCHEMA, "r")) == NULL) {
(void)fprintf(stderr, "Can't open %s, %s\n", SCHEMA, strerror(errno));
exit(1);
}
for (i = 0; fgets(buff, sizeof buff, F) != NULL; i++)
continue;
(void)fseek(F, (OFFSET_T)0, SEEK_SET);
ARTfields = NEW(ARTOVERFIELD, i + 1);
/* Parse each field. */
for (fp = ARTfields; fgets(buff, sizeof buff, F) != NULL; ) {
/* Ignore blank and comment lines. */
if ((p = strchr(buff, '\n')) != NULL)
*p = '\0';
if ((p = strchr(buff, COMMENT_CHAR)) != NULL)
*p = '\0';
if (buff[0] == '\0')
continue;
if ((p = strchr(buff, ':')) != NULL) {
*p++ = '\0';
fp->HasHeader = EQ(p, "full");
}
else
fp->HasHeader = FALSE;
fp->Header = COPY(buff);
fp->Length = strlen(buff);
fp++;
}
ARTfieldsize = fp - ARTfields;
(void)fclose(F);
}
/*
** Read an article and create an overview line without the trailing
** newline. Returns pointer to static space or NULL on error.
*/
STATIC char *
OVERgen(name)
char *name;
{
static ARTOVERFIELD *Headers;
static BUFFER B;
register ARTOVERFIELD *fp;
register ARTOVERFIELD *hp;
register QIOSTATE *qp;
register char *colon;
register char *line;
register char *p;
register int i;
register int size;
register int ov_size;
register long lines;
struct stat Sb;
long t;
char value[10];
/* Open article. */
if ((qp = QIOopen(name, QIO_BUFFER)) == NULL)
return NULL;
if ((p = strrchr(name, '/')) != NULL)
name = p + 1;
/* Set up place to store headers. */
if (Headers == NULL) {
Headers = NEW(ARTOVERFIELD, ARTfieldsize);
for (hp = Headers, i = ARTfieldsize; --i >= 0; hp++)
hp->Length = 0;
}
for (hp = Headers, i = ARTfieldsize; --i >= 0; hp++)
hp->HasHeader = FALSE;
for ( ; ; ) {
/* Read next line. */
if ((line = QIOread(qp)) == NULL) {
if (QIOtoolong(qp))
continue;
/* Error or EOF (in headers!?); shouldn't happen. */
QIOclose(qp);
return NULL;
}
/* End of headers? */
if (*line == '\0')
break;
/* See if we want this header. */
fp = ARTfields;
for (hp = Headers, i = ARTfieldsize; --i >= 0; hp++, fp++) {
colon = &line[fp->Length];
if (*colon != ':')
continue;
*colon = '\0';
if (!caseEQ(line, fp->Header)) {
*colon = ':';
continue;
}
*colon = ':';
if (fp->HasHeader)
p = line;
else
/* Skip colon and whitespace, store value. */
for (p = colon; *++p && ISWHITE(*p); )
continue;
size = strlen(p);
if (hp->Length == 0) {
hp->Length = size;
hp->Header = NEW(char, hp->Length + 1);
}
else if (hp->Length < size) {
hp->Length = size;
RENEW(hp->Header, char, hp->Length + 1);
}
(void)strcpy(hp->Header, p);
for (p = hp->Header; *p; p++)
if (*p == '\t' || *p == '\n')
*p = ' ';
hp->HasHeader = TRUE;
}
}
/* Read body of article, just to get lines. */
for (lines = 0; ; lines++)
if ((p = QIOread(qp)) == NULL) {
if (QIOtoolong(qp))
continue;
if (QIOerror(qp)) {
QIOclose(qp);
return NULL;
}
break;
}
/* Calculate total size, fix hardwired headers. */
ov_size = strlen(name) + ARTfieldsize + 2;
for (hp = Headers, fp = ARTfields, i = ARTfieldsize; --i >= 0; hp++, fp++) {
if (caseEQ(fp->Header, "Bytes") || caseEQ(fp->Header, "Lines")) {
if (fp->Header[0] == 'B' || fp->Header[0] == 'b')
t = fstat(QIOfileno(qp), &Sb) >= 0 ? (long)Sb.st_size : 0L;
else
t = lines;
(void)sprintf(value, "%ld", t);
size = strlen(value);
if (hp->Length == 0) {
hp->Length = size;
hp->Header = NEW(char, hp->Length + 1);
}
else if (hp->Length < size) {
hp->Length = size;
RENEW(hp->Header, char, hp->Length + 1);
}
(void)strcpy(hp->Header, value);
hp->HasHeader = TRUE;
}
if (hp->HasHeader)
ov_size += strlen(hp->Header);
}
/* Get space. */
if (B.Size == 0) {
B.Size = ov_size;
B.Data = NEW(char, B.Size + 1);
}
else if (B.Size < ov_size) {
B.Size = ov_size;
RENEW(B.Data, char, B.Size + 1);
}
/* Glue all the fields together. */
p = B.Data + strlen(strcpy(B.Data, name));
for (hp = Headers, i = ARTfieldsize; --i >= 0; hp++) {
*p++ = '\t';
if (hp->HasHeader)
p += strlen(strcpy(p, hp->Header));
}
*p = '\0';
QIOclose(qp);
return B.Data;
}
/*
** Take in a sorted list of count article numbers in group, and add
** them them to the overview file.
*/
STATIC void
AddLines(group, Adds)
char *group;
LIST *Adds;
{
static BUFFER New;
static BUFFER B;
static LINE *Lines;
static int LineSize;
register LINE *lp;
register char *next;
register int i;
register struct iovec *vp;
register ARTNUM *ap;
LINE *end;
struct iovec iov[8];
struct stat Sb;
char *p;
char file[SPOOLNAMEBUFF];
char lockfile[SPOOLNAMEBUFF];
int LineUsed;
int fd;
int lfd;
if (Verbose) {
for (ap = Adds->Articles, i = Adds->Used; --i >= 0; ap++)
(void)printf("+ %s/%ld\n", group, *ap);
if (DoNothing)
return;
}
/* Get space. */
if (New.Data == NULL) {
New.Size = 1024;
New.Data = NEW(char, New.Size);
LineSize = Adds->Size + 1;
Lines = NEW(LINE, LineSize);
}
else if (LineSize < Adds->Size) {
LineSize = Adds->Size + 1;
RENEW(Lines, LINE, LineSize);
}
New.Used = 0;
for (lp = Lines, ap = Adds->Articles, i = Adds->Used; --i >= 0; ap++) {
/* Get the overview data. */
if (InSpoolDir)
(void)sprintf(file, "%s/%ld", group, *ap);
else
(void)sprintf(file, "%s/%s/%ld", SPOOL, group, *ap);
if ((p = OVERgen(file)) == NULL)
continue;
/* Add it to the buffer and the lines array. */
lp->Article = *ap;
lp->Length = strlen(p);
lp->Offset = New.Used;
while (New.Size < New.Used + lp->Length + 1) {
New.Size *= 2;
RENEW(New.Data, char, New.Size);
}
(void)strcpy(&New.Data[New.Used], p);
New.Used += lp->Length++;
New.Data[New.Used++] = '\n';
lp++;
}
LineUsed = lp - Lines;
/* Turn offsets into real pointers. */
for (i = 0, lp = Lines; i < LineUsed; i++, lp++)
lp->Start = New.Data + lp->Offset;
/* Lock the group. */
(void)sprintf(lockfile, "%s/.LCK%s", group, _PATH_OVERVIEW);
lfd = open(lockfile, O_WRONLY | O_TRUNC | O_CREAT, ARTFILE_MODE);
if (lfd < 0) {
(void)fprintf(stderr, "Can't open %s, %s\n", lockfile, strerror(errno));
return;
}
/* Open file, lock it. */
(void)sprintf(file, "%s/%s", group, _PATH_OVERVIEW);
for ( ; ; ) {
if ((fd = open(file, O_RDWR | O_CREAT, ARTFILE_MODE)) < 0) {
(void)fprintf(stderr, "Can't open %s, %s\n", file, strerror(errno));
UnlockGroup(lfd, lockfile);
return;
}
if (LockFile(fd, FALSE) >= 0)
break;
/* Wait for lock; close file -- might be unlinked -- and try again. */
(void)LockFile(fd, TRUE);
(void)close(fd);
}
if (fstat(fd, &Sb) < 0) {
(void)fprintf(stderr, "Can't open %s, %s\n", file, strerror(errno));
UnlockGroup(lfd, lockfile);
(void)close(fd);
return;
}
if (Sb.st_size != 0) {
/* Read in the whole file. */
if (B.Size == 0) {
B.Size = Sb.st_size + 1;
B.Data = NEW(char, B.Size);
}
else if (B.Size < Sb.st_size) {
B.Size = Sb.st_size + 1;
RENEW(B.Data, char, B.Size);
}
if (xread(fd, B.Data, Sb.st_size) < 0) {
(void)fprintf(stderr, "Can't read %s, %s\n",
file, strerror(errno));
UnlockGroup(lfd, lockfile);
(void)close(fd);
return;
}
B.Data[Sb.st_size] = '\0';
/* Count lines, get space. */
for (i = 1, p = B.Data; (p = strchr(p, '\n')) != NULL && *++p; i++)
continue;
if (LineSize < i + 1 + LineUsed) {
LineSize = i + 1 + LineUsed;
RENEW(Lines, LINE, LineSize);
}
/* Add to lines array. */
for (lp = Lines + LineUsed, p = B.Data; ; p = next, lp++) {
if ((next = strchr(p, '\n')) == NULL)
break;
lp->Start = p;
lp->Length = ++next - p;
lp->Article = atol(p);
}
qsort((POINTER)Lines, (SIZE_T)(lp - Lines), sizeof lp[0],
LINEcompare);
LineUsed = lp - Lines;
}
/* Remove duplicates. */
for (end = lp - 1, lp = Lines; lp < end; lp++)
if (lp[0].Article == lp[1].Article)
lp->Article = 0;
/* Scan through lines, collecting rocks and holes. */
iov[0].iov_len = 0;
for (vp = iov, lp = Lines; lp < end + 1; lp++) {
/* An already-removed article, or one that should be? */
if (lp->Article == 0)
continue;
/* We're keeping this entry; see if we can add it to any in-progress
* iov element. */
if (vp->iov_len) {
if (vp->iov_base + vp->iov_len == lp->Start) {
/* Contiguous. */
vp->iov_len += lp->Length;
continue;
}
/* Doesn't fit -- get a new element. */
if (++vp == ENDOF(iov)) {
if (xwritev(lfd, iov, SIZEOF(iov)) < 0) {
(void)fprintf(stderr, "Can't write %s, %s\n",
lockfile, strerror(errno));
UnlockGroup(lfd, lockfile);
(void)close(fd);
return;
}
vp = iov;
}
}
/* Start new element. */
vp->iov_base = lp->Start;
vp->iov_len = lp->Length;
}
if (vp->iov_len)
vp++;
/* Write out remaining. */
if (iov[0].iov_len && xwritev(lfd, iov, vp - iov) < 0) {
(void)fprintf(stderr, "Can't write %s, %s\n",
lockfile, strerror(errno));
UnlockGroup(lfd, lockfile);
(void)close(fd);
return;
}
if (rename(lockfile, file) < 0)
(void)fprintf(stderr, "Can't rename %s, %s\n",
lockfile, strerror(errno));
/* Don't call UnlockGroup; do it inline. */
if (close(lfd) < 0)
(void)fprintf(stderr, "expireover cant close %s %s\n",
file, strerror(errno));
if (close(fd) < 0)
(void)fprintf(stderr, "expireover cant close unlinked %s %s\n",
file, strerror(errno));
}
/*
** Expire by batch, or line at a time.
*/
STATIC void
Expire(SortedInput, qp)
BOOL SortedInput;
register QIOSTATE *qp;
{
static LIST List;
register char *line;
register char *p;
char group[SPOOLNAMEBUFF];
if (List.Articles == NULL) {
List.Size = START_LIST_SIZE;
List.Articles = NEW(ARTNUM, List.Size);
}
List.Used = 0;
if (SortedInput) {
for ( ; ; ) {
if ((line = QIOread(qp)) == NULL) {
if (QIOerror(qp)) {
(void)fprintf(stderr, "Can't read input %s\n",
strerror(errno));
break;
}
if (QIOtoolong(qp))
continue;
break;
}
if ((p = strrchr(line, '/')) == NULL)
continue;
*p++ = '\0';
if (List.Used == 0) {
(void)strcpy(group, line);
List.Used = 0;
}
else if (!EQ(line, group)) {
LISTsort(&List);
RemoveLines(group, &List);
(void)strcpy(group, line);
List.Used = 0;
}
LISTappend(List, atol(p));
}
/* Do the last group. */
if (List.Used) {
LISTsort(&List);
RemoveLines(group, &List);
}
}
else {
for (List.Used = 1; ; ) {
if ((line = QIOread(qp)) == NULL) {
if (QIOerror(qp)) {
(void)fprintf(stderr, "Can't read input %s\n",
strerror(errno));
break;
}
if (QIOtoolong(qp))
continue;
break;
}
if ((p = strrchr(line, '/')) == NULL)
continue;
*p++ = '\0';
List.Articles[0] = atol(p);
RemoveLines(line, &List);
}
}
QIOclose(qp);
}
/*
** Read the overview file, return sorted list of all articles in it.
*/
STATIC LIST *
GetOverviewList(group)
char *group;
{
static LIST List;
register QIOSTATE *qp;
register char *p;
char file[SPOOLNAMEBUFF];
/* Open the file. */
(void)sprintf(file, "%s/%s", group, _PATH_OVERVIEW);
if ((qp = QIOopen(file, QIO_BUFFER)) == NULL)
return NULL;
/* Setup the article list. */
if (List.Articles == NULL) {
List.Size = START_LIST_SIZE;
List.Articles = NEW(ARTNUM, List.Size);
}
List.Used = 0;
/* Read all lines, picking up the article number. */
for ( ; ; ) {
if ((p = QIOread(qp)) == NULL) {
if (QIOerror(qp)) {
(void)fprintf(stderr, "Can't read %s, %s\n",
file, strerror(errno));
QIOclose(qp);
return NULL;
}
if (QIOtoolong(qp))
continue;
break;
}
LISTappend(List, atol(p));
}
QIOclose(qp);
if (List.Used == 0)
return NULL;
LISTsort(&List);
return &List;
}
/*
** Read spool directory and return sorted list of articles or NULL on error.
*/
STATIC LIST *
GetSpoolList(group)
char *group;
{
static LIST List;
register DIR *dp;
register DIRENTRY *ep;
register char *p;
char buff[SPOOLNAMEBUFF];
/* Open directory. */
if (InSpoolDir)
(void)strcpy(buff, group);
else
(void)sprintf(buff, "%s/%s", SPOOL, group);
if ((dp = opendir(buff)) == NULL)
return NULL;
/* Setup article list. */
if (List.Articles == NULL) {
List.Size = START_LIST_SIZE;
List.Articles = NEW(ARTNUM, List.Size);
}
List.Used = 0;
/* Get all articles. */
while ((ep = readdir(dp)) != NULL) {
p = ep->d_name;
if (!CTYPE(isdigit, p[0]) || strspn(p, "0123456789") != strlen(p))
continue;
LISTappend(List, atol(p));
}
(void)closedir(dp);
if (List.Used == 0)
return NULL;
LISTsort(&List);
return &List;
}
/*
** Return a list of all articles in the Over list that are not in
** the Spool list. Both lists are sorted. See SpoolUpdate for an
** explanation of the names.
*/
STATIC LIST *
GetNotIn(Over, Spool)
register LIST *Over;
register LIST *Spool;
{
static LIST List;
register ARTNUM *oEnd;
register ARTNUM *sEnd;
register ARTNUM *o;
register ARTNUM *s;
/* Setup the list. */
if (List.Articles == NULL) {
List.Size = START_LIST_SIZE;
List.Articles = NEW(ARTNUM, List.Size);
}
List.Used = 0;
o = Over->Articles;
s = Spool->Articles;
oEnd = o + Over->Used;
sEnd = s + Spool->Used;
while (o != oEnd && s != sEnd) {
if (*o < *s) {
LISTappend(List, *o++);
continue;
}
if (*o == *s)
o++;
s++;
}
/* If we hit the end of the Spool, then add everything else in the
* Overview. */
if (s == sEnd) {
while (o != oEnd)
LISTappend(List, *o++);
}
return List.Used ? &List : NULL;
}
/*
** Try to make one directory. Return FALSE on error.
*/
STATIC BOOL
MakeDir(Name)
char *Name;
{
struct stat Sb;
if (mkdir(Name, GROUPDIR_MODE) >= 0)
return TRUE;
/* See if it failed because it already exists. */
return stat(Name, &Sb) >= 0 && S_ISDIR(Sb.st_mode);
}
/*
** Given a directory, comp/foo/bar, create that directory and all
** intermediate directories needed. Return FALSE on error.
*/
BOOL
MakeOverDir(Name)
register char *Name;
{
register char *p;
BOOL made;
/* Optimize common case -- parent almost always exists. */
if (MakeDir(Name))
return TRUE;
/* Try to make each of comp and comp/foo in turn. */
for (p = Name; *p; p++)
if (*p == '/') {
*p = '\0';
made = MakeDir(Name);
*p = '/';
if (!made)
return FALSE;
}
return MakeDir(Name);
}
/*
** Update using the News Spool. Either add or delete entries.
*/
STATIC void
SpoolUpdate(AddEntries, Name)
BOOL AddEntries;
char *Name;
{
register QIOSTATE *qp;
register char *line;
register char *p;
LIST *Over;
LIST *Spool;
LIST *Missing;
/* Open file. */
if (EQ(Name, "-"))
qp = QIOfdopen(STDIN, QIO_BUFFER);
else if ((qp = QIOopen(Name, QIO_BUFFER)) == NULL) {
(void)fprintf(stderr, "Can't open %s, %s\n", Name, strerror(errno));
exit(1);
}
if (AddEntries)
ARTreadschema();
for ( ; ; ) {
if ((line = QIOread(qp)) == NULL) {
if (QIOtoolong(qp) || QIOerror(qp)) {
(void)fprintf(stderr,
"Line too long or error reading %s, %s\n",
Name, strerror(errno));
exit(1);
}
break;
}
/* Nip off newsgroup name, and turn it into a directory. */
for (p = line; *p && !ISWHITE(*p) && *p != '\n'; p++)
if (*p == '.')
*p = '/';
*p = '\0';
if (AddEntries) {
if ((Spool = GetSpoolList(line)) == NULL)
continue;
if ((Over = GetOverviewList(line)) != NULL) {
if ((Missing = GetNotIn(Spool, Over)) != NULL)
AddLines(line, Missing);
}
else if (!InSpoolDir) {
if (MakeOverDir(line))
AddLines(line, Spool);
else
(void)fprintf(stderr, "expireover: cant mkdir %s, %s\n",
line, strerror(errno));
}
else
AddLines(line, Spool);
continue;
}
if ((Over = GetOverviewList(line)) == NULL)
continue;
if ((Spool = GetSpoolList(line)) != NULL) {
if ((Missing = GetNotIn(Over, Spool)) != NULL)
RemoveLines(line, Missing);
}
else
RemoveLines(line, Over);
}
QIOclose(qp);
exit(0);
}
/*
** Print usage message and exit.
*/
STATIC NORETURN
Usage()
{
(void)fprintf(stderr, "Usage: expireover [flags] [file...]\n");
exit(1);
}
int
main(ac, av)
int ac;
char *av[];
{
register int i;
QIOSTATE *qp;
BOOL AddEntries;
BOOL ReadSpool;
BOOL SortedInput;
char *Dir;
char *Name;
/* Set defaults. */
Dir = _PATH_OVERVIEWDIR;
Name = _PATH_ACTIVE;
AddEntries = FALSE;
ReadSpool = FALSE;
SortedInput = FALSE;
(void)umask(NEWSUMASK);
/* Parse JCL. */
while ((i = getopt(ac, av, "aD:f:nO:svz")) != EOF)
switch (i) {
default:
Usage();
/* NOTREACHED */
case 'a':
AddEntries = TRUE;
ReadSpool = TRUE;
break;
case 'D':
Dir = optarg;
break;
case 'f':
Name = optarg;
break;
case 'n':
DoNothing = TRUE;
break;
case 'O':
SCHEMA = optarg;
break;
case 's':
ReadSpool = TRUE;
break;
case 'v':
Verbose = TRUE;
break;
case 'z':
SortedInput = TRUE;
break;
}
ac -= optind;
av += optind;
if ((ReadSpool && ac) || (AddEntries && !ReadSpool))
Usage();
/* Setup. */
if (chdir(Dir) < 0) {
(void)fprintf(stderr, "Cant chdir to %s, %s\n", Dir, strerror(errno));
exit(1);
}
InSpoolDir = EQ(Dir, SPOOL);
/* Do work. */
if (ReadSpool)
SpoolUpdate(AddEntries, Name);
if (ac == 0)
Expire(SortedInput, QIOfdopen(STDIN, QIO_BUFFER));
else {
for ( ; *av; av++)
if (EQ(*av, "-"))
Expire(SortedInput, QIOfdopen(STDIN, QIO_BUFFER));
else if ((qp = QIOopen(*av, QIO_BUFFER)) == NULL)
(void)fprintf(stderr, "Can't open %s, %s\n",
*av, strerror(errno));
else
Expire(SortedInput, qp);
}
exit(0);
/* NOTREACHED */
}