* 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
* 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
static char *SccsId
= "@(#)expire.c 2.55 10/15/87";
char *Progname
= "expire"; /* used by xerror to identify failing program */
/* Number of array entries to allocate at a time. */
#define SPACE_INCREMENT 1000
time_t e_droptime
, e_expiretime
;
char NARTFILE
[BUFLEN
], OARTFILE
[BUFLEN
];
char PAGFILE
[BUFLEN
], DIRFILE
[BUFLEN
];
char NACTIVE
[BUFLEN
], OACTIVE
[BUFLEN
];
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 */
extern char filename
[], nbuf
[];
* This code uses realloc to get more of the multhist array.
extern char *calloc(), *realloc();
time_t cgtdate(), time();
* Try to run as NEWSUSR/NEWSGRP
if ((pw
= getpwnam(NEWSUSR
)) == NULL
)
xerror("Cannot get NEWSUSR pw entry");
if ((gp
= getgrnam(NEWSGRP
)) == NULL
)
xerror("Cannot get NEWSGRP gr entry");
if (signal(SIGHUP
, SIG_IGN
) != SIG_IGN
)
if (signal(SIGINT
, SIG_IGN
) != SIG_IGN
)
verbose
= argv
[1][2] - '0';
else if (argc
> 2 && argv
[2][0] != '-') {
setbuf(stdout
, (char *)NULL
);
case 'e': /* Use this as default expiration time */
if (argc
> 2 && argv
[2][0] != '-') {
expincr
= atol(argv
[1]) * DAYS
;
} else if (isdigit(argv
[1][2]))
expincr
= atol(&argv
[1][2]) * DAYS
;
case 'E': /* Use this as default forget time */
if (argc
> 2 && argv
[2][0] != '-') {
dropincr
= atol(argv
[1]) * DAYS
;
} else if (isdigit(argv
[1][2]))
dropincr
= atol(&argv
[1][2]) * DAYS
;
case 'I': /* Ignore any existing expiration date */
case 'i': /* Ignore any existing expiration date */
while (argc
> 1 && argv
[1][0] != '-') {
argvlen
= strlen(argv
[1]);
if (ngpatlen
+ argvlen
+ 2 > sizeof (ngpat
)) {
xerror("Too many groups specified for -n\n");
if (ngpat
[ngpatlen
] == '\0') {
strcpy(&ngpat
[ngpatlen
], argv
[1]);
case 'a': /* archive expired articles */
if (access(OLDNEWS
,0) < 0){
xerror("No archiving possible\n");
while (argc
> 1 && argv
[1][0] != '-') {
argvlen
= strlen(argv
[1]);
if (arpatlen
+ argvlen
+ 2 > sizeof (arpat
)) {
xerror("Too many groups specified for -a\n");
if (arpat
[arpatlen
] == '\0') {
strcpy(&arpat
[arpatlen
], argv
[1]);
case 'h': /* ignore history */
case 'r': /* rebuild history file */
case 'R': /* just rebuild the dbm files */
fprintf(stderr
, "You have not compiled expire with DBM, so -R is meaningless\n");
case 'p': /* use posting date to expire */
case 'f': /* expire messages from baduser */
strcpy(baduser
, argv
[2]);
case 'u': /* update the active file from 2.10.1 fmt */
case 'H': /* convert to history.d format */
printf("Usage: expire [ -v [level] ] [-e days ] [-i] [-a] [-r] [-h] [-p] [-u] [-f username] [-n newsgroups] [-H]\n");
if (dropincr
< expincr
) {
fprintf(stderr
, "History expiration time < article expiration time. Default used.\n");
(void) strcpy(ngpat
, "all,");
(void) strcpy(arpat
, "all,");
xerror("Cannot chdir %s", SPOOL
);
printf("expire: nohistory %d, rebuild %d, doarchive %d\n",
nohistory
, dorebuild
, doarchive
);
printf("newsgroups: %s\n",ngpat
);
printf("archiving: %s\n",arpat
);
(void) sprintf(OARTFILE
, "%s/%s", LIB
, "ohistory");
(void) sprintf(NARTFILE
, "%s/%s", LIB
, "nhistory");
(void) sprintf(OACTIVE
, "%s/%s", LIB
, "oactive");
(void) sprintf(NACTIVE
, "%s/%s", LIB
, "nactive");
* Now read in any saved news.
monitor((int(*)())0,(int(*)())0,0,0,0);
/*afline happens to be available - (we're getting out anyway)*/
sprintf(afline
, "%s/%s", logdir(HOME
), RNEWS
);
execl(afline
, "rnews", "-U", (char *)NULL
);
execl(RNEWS
, "rnews", "-U", (char *)NULL
);
register char *p1
, *p2
, *p3
;
register FILE *fp
= NULL
;
static struct direct
*ngdir
;
(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));
ohfd
= xfopen(ACTIVE
, "r");
/* Allocate initial space for multiple newsgroup (for
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",
if ((nhfd
= popen(afline
, "w")) == NULL
)
xerror("Cannot exec %s", afline
);
nhfd
= xfopen("/dev/null", "w");
ohfd
= xfopen(ARTFILE
, "r");
ohfd
= nexthistfile((FILE *)NULL
);
nhfd
= xfopen(NARTFILE
, "w");
if (fgets(afline
, BUFLEN
, ohfd
) == NULL
)
(void) strcpy(nbuf
, afline
);
if (!ngmatch(nbuf
, ngpat
))
/* Change a group name from
if ((ngdirp
= opendir(nbuf
)) == NULL
)
/* Continue looking if not an article. */
} while (ngdir
== NULL
|| !islegal(fn
,nbuf
,ngdir
->d_name
));
printf("article: %s\n", fn
);
strcpy(filename
, dirname(fn
));
fp
= access(filename
, 04) ? NULL
: art_open(filename
, "r");
if (fgets(afline
, BUFLEN
, ohfd
) == NULL
)
if (fgets(afline
, BUFLEN
, ohfd
) == NULL
)
if (!(ohfd
= nexthistfile(ohfd
)))
printf("article: %s", afline
);
p1
= index(afline
, '\t');
(void) strcpy(h
.ident
, afline
);
p2
= index(p1
+ 1, '\t');
(void) strcpy(recdate
, p1
+1);
(void) strcat(recdate
, " GMT");
rectime
= cgtdate(recdate
);
* convert list of newsgroups from
while (*p4
!= '\0' && *p4
!= ' ')
* 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.
if (!ngmatch(nbuf
, ngpat
) ||
((rectime
+expincr
> today
) && !dorebuild
&&
!frflag
&& !usepost
&& recdate
[0] != ' '))
if (!dorebuild
&& !frflag
&& !usepost
&&
goto nailit
; /* just expire it */
* Look for the file--possibly several times,
* if it was posted to several news groups.
strcpy(filename
, dirname(p3
));
if (access(filename
, 4) == 0 &&
((fp
=art_open(filename
, "r")) != NULL
))
* this probably means that the article has been
* cancelled. Lets assume that, and make an
* entry in the history file to that effect.
strcpy(p2
, "cancelled\n");
if (h
.unrec
[i
] != NULL
) {
if (!hread(&h
, fp
, TRUE
)) {
printf("Garbled article %s.\n", filename
);
* 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 ..
if (recdate
[0] == '\0') {
if (fstat(fileno(fp
), &statb
) < 0)
rectime
= cgtdate(h
.subdate
);
rectime
= statb
.st_mtime
;
rectime
= cgtdate(recdate
);
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.
if ((cp
= index(h
.nbuf
, NGDELIM
)) == NULL
) {
"%s\t%s%2.2d/%2.2d/%d %2.2d:%2.2d\t%s\n",
"%s\t%s%02d/%02d/%d %02d:%02d\t%s\n",
h
.ident
, h
.expdate
[0] ? " " : "",
tm
->tm_mon
+1, tm
->tm_mday
, tm
->tm_year
,
tm
->tm_hour
, tm
->tm_min
, filename
)
xerror("History write failed");
for (mhp
= multhist
; mhp
< multhist
+mh_size
&& mhp
->mh_ident
!= NULL
; mhp
++) {
if (mhp
->mh_file
== NULL
)
if (strcmp(mhp
->mh_ident
, h
.ident
))
(void) strcat(filename
, " ");
(void) strcat(filename
, mhp
->mh_file
);
* if we have all the links, write to hist now
if (chrcnt(filename
, ' ') == chrcnt(cp
,NGDELIM
))
* 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
if (mhp
>= multhist
+ mh_size
) {
multhist
= (struct multhist
*)
realloc ((char *)multhist
,
sizeof (struct multhist
) *
(SPACE_INCREMENT
+ mh_size
));
xerror("Too many articles with multiple newsgroups");
for (mhp
= multhist
+ mh_size
;
mhp
< multhist
+mh_size
+SPACE_INCREMENT
;
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);
(void) strcpy(cp
, filename
);
exptime
= cgtdate(h
.expdate
);
newtime
= (usepost
? cgtdate(h
.subdate
) : rectime
) + expincr
;
if (!h
.expdate
[0] || ignorexp
== 2 ||
(ignorexp
== 1 && newtime
< exptime
))
if (frflag
? strcmp(baduser
,h
.from
)==0 : today
>= exptime
) {
printf("cancel %s\n", filename
);
printf("cancel %s\n", h
.ident
);
(void) sprintf(p2
, "%s\n", grpsleft
);
if (verbose
> 2 && grpsleft
[0])
printf("Some good in %s\n", h
.ident
);
printf("Good article %s\n", h
.ident
);
if (grpsleft
[0] == '\0' && today
>= rectime
+ dropincr
) {
printf("Drop history of %s - %s\n",
printf("Retain history of %s - %s\n",
if (fputs(afline
, nhfd
) == EOF
)
xerror("history write failed");
register struct multhist
*mhp
;
for (mhp
= multhist
; mhp
< multhist
+mh_size
&& mhp
->mh_ident
!= NULL
; mhp
++)
if (mhp
->mh_file
!= NULL
) {
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 ((fp
= art_open(filename
, "r")) == NULL
) {
printf("Can't open %s.\n", filename
);
if (!hread(&h
, fp
, TRUE
)) {
printf("Garbled article %s.\n", filename
);
if (fstat(fileno(fp
), &statb
) < 0)
rectime
= cgtdate(h
.subdate
);
rectime
= statb
.st_mtime
;
"%s\t%s%2.2d/%2.2d/%d %2.2d:%2.2d\t%s\n",
"%s\t%s%02d/%02d/%d %02d:%02d\t%s\n",
h
.ident
, h
.expdate
[0] ? " " : "",
tm
->tm_mon
+1, tm
->tm_mday
, tm
->tm_year
,
tm
->tm_hour
, tm
->tm_min
, mhp
->mh_file
)
xerror("History write failed");
xerror("History write failed, %s", errmsg(errno
));
if (dorebuild
|| !nohistory
) {
(void) rename(ARTFILE
, OARTFILE
);
(void) rename(NARTFILE
, ARTFILE
);
(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
);
#if defined(BSD4_2) || defined(LOCKF)
/* set up exclusive locking so inews does not run while expire does */
#if defined(BSD4_2) || defined(LOCKF)
LockFd
= open(ACTIVE
, 2);
if (lockf(LockFd
, F_LOCK
, 0L) < 0)
if (flock(LockFd
, LOCK_EX
) < 0)
xerror("Can't get lock for expire: %s", errmsg(errno
));
#else /* !BSD4_2 && !LOCKF */
sprintf(afline
,"%s.lock", ACTIVE
);
while (LINK(ACTIVE
, afline
) < 0 && errno
== EEXIST
) {
xerror("Can't get lock for expire");
#endif /* !BSD4_2 && !LOCKF */
#if defined(BSD4_2) || defined(LOCKF)
sprintf(bfr
, "%s.lock", ACTIVE
);
static struct direct
*ngdir
;
printf("updating active file %s\n", ACTIVE
);
ohfd
= xfopen(ACTIVE
, "r");
nhfd
= xfopen(NACTIVE
, "w");
if (fgets(afline
, BUFLEN
, ohfd
) == NULL
)
if (sscanf(afline
,"%s %ld %ld %c",nbuf
,&maxart
, &minart
,
xerror("Active file corrupt");
printf("looking at group %s\n", nbuf
);
if (!ngmatch(nbuf
, ngpat
)) {
if (fputs(afline
, nhfd
) == EOF
)
xerror("active file write failed");
/* Change a group name from a.b.c to a/b/c */
hassubs
= stat(nbuf
, &stbuf
) != 0 || stbuf
.st_nlink
!= 2;
if ((ngdirp
= opendir(nbuf
)) != NULL
) {
while (ngdir
= readdir(ngdirp
)) {
(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
)) )
printf("\tmaxart = %5.5ld, minart = %5.5ld\n",
if (fprintf(nhfd
,"%s %5.5ld %5.5ld %c\n", afline
, maxart
,
xerror("Active file write failed");
printf("\tmaxart = %05ld, minart = %05ld\n",
if (fprintf(nhfd
,"%s %05ld %05ld %c\n", afline
, maxart
,
xerror("Active file write failed");
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'. */
printf("ulall '%s', '%s'\n", artlist
, hp
->subdate
);
while (*artlist
== ' ' || *artlist
== '\n' || *artlist
== ',')
p
= index(artlist
, '\n');
strcpy(newname
, artlist
);
q
= index(newname
, '\0');
if (q
== artlist
) /* null -> the end */
/* should be impossible to get here */
if (ngmatch(newname
, ngpat
)) {
if (ngmatch(newname
, arpat
)) {
q
= fn
+ strlen(SPOOL
) + 1;
(void) sprintf(newname
, "%s/%s", OLDNEWS
, q
);
printf("link %s to %s\n", fn
, newname
);
if (LINK(fn
, newname
) == -1) {
if (mkparents(newname
) == 0)
if (LINK(fn
, newname
) == -1)
timep
[0] = timep
[1] = cgtdate(hp
->subdate
);
(void) utime(newname
, timep
);
printf("unlink %s\n", fn
);
if (UNLINK(fn
) < 0 && errno
!= ENOENT
)
printf("retain %s (%s)\n", hp
->ident
, fn
);
strcat(grpsleft
, artlist
);
f2
= creat(newname
,0644);
while((r
=read(f1
, buf
, BUFSIZ
)) > 0)
* Count instances of c in s
* If any parent directories of this dir don't exist, create them.
(void) strcpy(buf
, fullname
);
if ((rc
= mkdir(buf
, 0755)) < 0)
printf("mkdir %s, rc %d\n", buf
, rc
);
/* Make sure this file is a legal article. */
islegal(fullname
, path
, name
)
(void) sprintf(fullname
, "%s/%s", path
, name
);
/* make sure the article is numeric. */
if (!isascii(*name
) || !isdigit(*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
, '/'))
* 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.
(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");
while (fpos
=ftell(fd
), fgets(lb
, BUFSIZ
, fd
) != NULL
) {
remember(article
, fileoff
)
lhs
.dsize
= strlen(article
) + 1;
rhs
.dptr
= (char *) &fileoff
;
rhs
.dsize
= sizeof fileoff
;
printf("remember: %s @ %ld\n", article
, fileoff
);
xerror("dbm store failed");
* Open the next history subdirectory file
static int histfilecounter
= -1;
if (++histfilecounter
> 9)
sprintf(bfr
, "%s.d/%d", ARTFILE
, histfilecounter
);
printf("reading history file %s\n", bfr
);
* Rebuild the history subdirectory from LIBDIR/history
char fn
[BUFLEN
], ofn
[BUFLEN
];
/* rebuild history subfiles */
(void) sprintf(fn
, "%s.od", ARTFILE
);
(void) sprintf(fn
, "%s.d", ARTFILE
);
printf("Rebuilding history subfile directory %s.\n", fn
);
for (i
= 0; i
< 10; i
++) {
(void) sprintf(fn
, "%s.d/%c", ARTFILE
, i
+ '0');
(void) sprintf(ofn
, "%s.od/%c", ARTFILE
, i
+ '0');
subfd
[i
] = xfopen(fn
, "w+");
ohfd
= xfopen(ARTFILE
, "r");
while (fgets(fn
, BUFLEN
, ohfd
) != NULL
) {
i
= findhfdigit(fn
) - '0';
if (ferror(subfd
[i
]) || fclose(subfd
[i
]))
xerror("History subfile write");