[alt.sources] reap: expire news as space needed

dt@yenta.alb.nm.us (David B. Thomas) (11/27/90)

Hello, netters!  Here's an expire alternative.  If you have a small disk,
this is for you.  It was inspired by Mike Murphy's "trasher" posted a few
weeks ago.

Problem:
	News volume fluctuates wildly...sites with small disks must either
expire aggresively, often deleting more than necessary, or worry that an
unexpected huge dose of news might overfill the disk.


Solution:
	Implement a tool that expires according to a user-defined scheme
until sufficient freespace is reclaimed, then stops, leaving as much
juicy news online as is feasible.  Reap does this.


Expire Does Two Jobs:
	Both Bnews and Cnews expires really do two jobs:
		1. trim history files
		2. delete outdated articles
Thanks to some inspired jootsing (acronym for "jumping out of the system")
by Mike Murphy (mrm@sceard.com) and others, it is more than possible to
separate those two functions.  This is, of course, in keeping with the
unix philosophy of one tool doing one job well!


What Reap Does:
	Reap only takes care of the second job: deleting old articles.
It works by checking freespace, and processing one line at a time from a
list of expire functions, until the desired freespace is attained.
Each expire function consists of an age limit in days (decimals okay)
and a list of newsgroups to process or not process, sys file style.  Ex:

	.5	alt.sex.pictures,talk,!talk.bizarre,junk
	1	rec,!rec.games,!rec.humor

This example would check freespace, and if more space is needed, expire
to .5 days everything in alt.sex.pictures,talk (except for talk.bizarre)
and junk.  Then it would stop and check freespace again.  If still more
space is needed, it would expire to 1 day everything in rec except
rec.games.* and rec.humor.*.  It's that simple.


Okay...So How Do I Arrange To Trim The History Files, Since Reap Can't:
	Included in this distribution is a shell script (mostly written
by Mike Murphy) to handle Cnews history files.  It shouldn't be too
difficult to do something similar for Bnews, or you can give in and use
the original expire utility with options that tell it to expire the
history files only...but I think that would be slow.  It just comes down
to removing lines from ordinary text files, based on their contents.
Murphy and I used awk.


But Is It Fast:
	Yes, largely because it doesn't have to do much.  Even "find | rm"
is slower because find is repeatedly exec-ing rm.

	Since the functions of deleting articles and trimming history
are separate, I now run reap every few hours, but trim the history list
just once a day.  That effectively keeps my disk space up to snuff, but
only thrashes at the history file in the middle of the night.


Credits:
	I owe a lot to Mike Murphy for inspiring me with his "trasher"
system.  I also owe a lot to all of your netters who will flood me with
suggestions and improvements in the coming weeks (hint, hint!).

Note: this uses ustat(2), a system-V call to find disk freespace.  It should
be a very easy hack to use the BSD equivalent.  I just don't know how to do
it.

						little david
						dt@yenta.alb.nm.us

-----snip-----------snip-----------snip-----------snip-----------snip------
#!/bin/sh
# This is a shell archive (shar 3.44)
# made 11/27/1990 07:20 UTC by dt@yenta
# Source directory /u/dt/reap
#
# existing files will NOT be overwritten unless -c is specified
#
# This shar contains:
# length  mode       name
# ------ ---------- ------------------------------------------
#    730 -rw-rw-r-- INSTALL
#    339 -rw-rw-r-- MANIFEST
#    900 -rw-rw-r-- Makefile
#   2792 -rw-rw-r-- README
#    969 -rwxrwxr-x exphist
#   2539 -rw-rw-r-- lib.c
#    873 -rw-rw-r-- list.sample
#   2028 -rw-rw-r-- main.c
#   2335 -rw-r--r-- reap.8
#   2150 -rw-rw-r-- reap.c
#    676 -rw-rw-r-- reap.h
#
# ============= INSTALL ==============
if test -f 'INSTALL' -a X"$1" != X"-c"; then
	echo 'x - skipping INSTALL (File already exists)'
else
echo 'x - extracting INSTALL (Text)'
sed 's/^X//' << 'SHAR_EOF' > 'INSTALL' &&
X
To install:
X
1. Edit tops of Makefile and main.c to fit your system.
2. make
3. Compose a sample function list file or use the sample provided to
X	test reap (use the -n option to avoid really removing files).
4. su and make install.
5. Arrange to trim your news system's history list regularly.  If you have
X	Cnews, try the "exphist" script provided, run from cron.  That
X	script requires that a tiny file, /usr/lib/news/histdays, contain
X	the number of days of history data to keep, or else you can hardcode
X	in your favorite number.  If you have Bnews, I don't have a nifty
X	suggestion, other than using expire(8) with a ridiculously long
X	article expire time, but a sane history expire time.
6. Arrange to run reap from cron.
SHAR_EOF
chmod 0664 INSTALL ||
echo 'restore of INSTALL failed'
Wc_c="`wc -c < 'INSTALL'`"
test 730 -eq "$Wc_c" ||
	echo 'INSTALL: original size 730, current size' "$Wc_c"
fi
# ============= MANIFEST ==============
if test -f 'MANIFEST' -a X"$1" != X"-c"; then
	echo 'x - skipping MANIFEST (File already exists)'
else
echo 'x - extracting MANIFEST (Text)'
sed 's/^X//' << 'SHAR_EOF' > 'MANIFEST' &&
Included with this distribution are:
X
README		- general description and credits
INSTALL		- hints on how to install reap and get it going
Xexphist		- history trimmer utility for Cnews
list.sample	- sample function script file for reap
reap.8		- man page
lib.c		- source
main.c		- source
reap.c		- source
reap.h		- source
Makefile	- makefile
SHAR_EOF
chmod 0664 MANIFEST ||
echo 'restore of MANIFEST failed'
Wc_c="`wc -c < 'MANIFEST'`"
test 339 -eq "$Wc_c" ||
	echo 'MANIFEST: original size 339, current size' "$Wc_c"
fi
# ============= Makefile ==============
if test -f 'Makefile' -a X"$1" != X"-c"; then
	echo 'x - skipping Makefile (File already exists)'
else
echo 'x - extracting Makefile (Text)'
sed 's/^X//' << 'SHAR_EOF' > 'Makefile' &&
#----------------------------
# parameters for installation
#----------------------------
X
# directories where manual and executable are to be installed
BINDIR = /usr/lib/newsbin/expire
MANDIR = /usr/man/man8
X
# owner, group and file mode for manual and executable
EXEOWNER = bin
EXEGROUP = bin
EXEMODE = 775
MANOWNER = bin
MANGROUP = bin
MANMODE = 664
X
X
# your favorite C compiler
#CC = cc
CC = shcc
X
# directory access functions library, if not in standard C library.
LIB = -ldirent
X
X
#-------------
# boring stuff
#-------------
X
OBJ = main.o lib.o reap.o
X
reap: $(OBJ)
X	$(CC) -s -o reap $(SHLIB) $(OBJ) $(LIB)
X
$(OBJ): reap.h
X
install: reap reap.8
X	cp reap $(BINDIR)
X	chown $(EXEOWNER) $(BINDIR)/reap
X	chgrp $(EXEGROUP) $(BINDIR)/reap
X	chmod $(EXEMODE) $(BINDIR)/reap
X	cp reap.8 $(MANDIR)
X	chmod $(MANMODE) $(MANDIR)/reap.8
X	chown $(MANOWNER) $(MANDIR)/reap.8
X	chgrp $(MANGROUP) $(MANDIR)/reap.8
SHAR_EOF
chmod 0664 Makefile ||
echo 'restore of Makefile failed'
Wc_c="`wc -c < 'Makefile'`"
test 900 -eq "$Wc_c" ||
	echo 'Makefile: original size 900, current size' "$Wc_c"
fi
# ============= README ==============
if test -f 'README' -a X"$1" != X"-c"; then
	echo 'x - skipping README (File already exists)'
else
echo 'x - extracting README (Text)'
sed 's/^X//' << 'SHAR_EOF' > 'README' &&
X
A Problem:
X	News volume fluctuates wildly...sites with small disks must either
Xexpire aggresively, often deleting more than necessary, or worry that an
unexpected huge dose of news might overfill the disk.
X
X
A Solution:
X	Implement a tool that expires according to a user-defined scheme
until sufficient freespace is reclaimed, then stops, leaving as much
juicy news online as is feasible.  Reap does this.
X
X
Expire Does Two Jobs:
X	Both Bnews and Cnews expires really do two jobs:
X		1. trim history files
X		2. delete outdated articles
Thanks to some inspired jootsing (acronym for "jumping out of the system")
by Mike Murphy (mrm@sceard.com) and others, it is more than possible to
separate those two functions.  This is, of course, in keeping with the
unix philosophy of one tool doing one job well!
X
X
What Reap Does:
X	Reap only takes care of the second job: deleting old articles.
It works by checking freespace, and processing one line at a time from a
list of expire functions, until the desired freespace is attained.
Each expire function consists of an age limit in days (decimals okay)
and a list of newsgroups to process or not process, sys file style.  Ex:
X
X	.5	alt.sex.pictures,talk,!talk.bizarre,junk
X	1	rec,!rec.games,!rec.humor
X
This example would check freespace, and if more space is needed, expire
to .5 days everything in alt.sex.pictures,talk (except for talk.bizarre)
and junk.  Then it would stop and check freespace again.  If still more
space is needed, it would expire to 1 day everything in rec except
rec.games.* and rec.humor.*.  It's that simple.
X
X
Okay...So How Do I Arrange To Trim The History Files, Since Reap Can't:
X	Included in this distribution is a shell script (mostly written
by Mike Murphy) to handle Cnews history files.  It shouldn't be too
difficult to do something similar for Bnews, or you can give in and use
the original expire utility with options that tell it to expire the
history files only...but I think this will be slow.  It just comes down
to removing lines from ordinary text files, based on their contents.
Murphy and I used awk.
X
X
But Is It Fast:
X	Yes, largely because it doesn't have to do much.  Even "find | rm"
is slower because find is repeatedly exec-ing rm.  "rm -rf" has me beat,
though, I'll bet! :-)
X
X	Since the functions of deleting articles and trimming history
are separate, I now run reap every six hours, but trim the history list
just once a day.  That effectively keeps my disk space up to snuff, but
only thrashes at the history file in the middle of the night.
X
X
Credits:
X	I owe a lot to Mike Murphy for inspiring me with his "trasher"
system.  I also owe a lot to all of your netters who will flood me with
suggestions and improvements in the coming weeks (hint, hint!).
X
X						little david
X						dt@yenta.alb.nm.us
SHAR_EOF
chmod 0664 README ||
echo 'restore of README failed'
Wc_c="`wc -c < 'README'`"
test 2792 -eq "$Wc_c" ||
	echo 'README: original size 2792, current size' "$Wc_c"
fi
# ============= exphist ==============
if test -f 'exphist' -a X"$1" != X"-c"; then
	echo 'x - skipping exphist (File already exists)'
else
echo 'x - extracting exphist (Text)'
sed 's/^X//' << 'SHAR_EOF' > 'exphist' &&
#! /bin/sh
# exphist - expire history file
X
# =()<. ${NEWSCONFIG-@<NEWSCONFIG>@}>()=
. ${NEWSCONFIG-/usr/lib/news/bin/config}
X
PATH=$NEWSCTL/bin:$NEWSBIN/expire:$NEWSBIN:$NEWSPATH ; export PATH
umask $NEWSUMASK
X
days=`cat /usr/lib/news/histdays`
X
lock="$NEWSCTL/LOCKexpire"
ltemp="$NEWSCTL/L.$$"
Xecho $$ >$ltemp
trap "rm -f $ltemp ; exit 0" 0 1 2 15
if newslock $ltemp $lock
then
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
fi
X
cd $NEWSCTL
rm -f history.n history.n.pag history.n.dir
now=`getdate now`
age=`expr 86400 \* $days`
ago=`expr $now - $age`
awk "{split(\$2,dates,\"~\");if(dates[1]>$ago)print \$0}" history >history.n
mkdbm history.n
[ -s history.n ] &&
mv history history.o &&    # install new ASCII history file
mv history.n history &&
rm -f history.pag &&       # and related dbm files
rm -f history.dir &&
mv history.n.pag history.pag &&
mv history.n.dir history.dir
Xexit 0
SHAR_EOF
chmod 0775 exphist ||
echo 'restore of exphist failed'
Wc_c="`wc -c < 'exphist'`"
test 969 -eq "$Wc_c" ||
	echo 'exphist: original size 969, current size' "$Wc_c"
fi
# ============= lib.c ==============
if test -f 'lib.c' -a X"$1" != X"-c"; then
	echo 'x - skipping lib.c (File already exists)'
else
echo 'x - extracting lib.c (Text)'
sed 's/^X//' << 'SHAR_EOF' > 'lib.c' &&
X
/*
X * lib.c
X *	subroutines needed by reap utility
X */
X
#include "reap.h"
X
/*
X * freeblox(devno)
X *
X *	assuming devno is the device number of a file system,
X *	this function returns the free space on that file system,
X *	in blocks (whatever that means for that filesystem) as a
X *	long int.
X *
X */
X
freeblox(devno)
int	devno;
{
X	static struct ustat	ust;
X
X	if (ustat(devno, &ust)) {
X		fprintf (stderr, "%s: ustat() failed\n", progname);
X		exit (-1);
X	}
X	return (ust.f_tfree);
X
} /* freeblox() */
X
X
X
X
/*
X * parse(cmd)
X *	execute a line from the script file
X *
X * valid command lines:
X *	a blank line is ignored
X *	# comment (ignored)
X *	days filespecs
X *	
X */
X
parse(cmd)
char	*cmd;
{
X	double		days;
X	char		*t,
X			*p,
X			*q,
X			*seps = ",\n ";
X
X
/* skip initial whitespace */
X	p = cmd;
X	while (isspace(*p))
X		++p;
X
/* ignore blank lines or comments */
X	if (*p == '\0' || *p == '#')
X		return(1);
X
/* read the expire time from the line as a floating point number,
X * then figure out what the timestamp on a file that old would be */
X	age = now - (time_t) (atof(p) * SECINDAY);
X
/* skip ahead to the filespec list */
X	while (!isspace(*p))
X		++p;
X	while (isspace(*p))
X		++p;
X
X	preclude();
X	for (t = strtok(p, seps); t; t = strtok(NULL, seps)) {
X	/* forbid starting with a slash */
X		if (*t == '/')
X			*t = '.';
X	/* change dots to slashes except in column 1 */
X		while ( (q = strchr(t+1,'.')) != NULL)
X			*q = '/';
X		if (*t == '!')
X			exclude (t+1);
X		else
X			include (t);
X	}
X
X	reap();
X
X	return (0);
X
} /* parse() */
X
X
X
/*
X * newnode,exclude, include, preclude
X *
X *	functions to maintain global linked lists:
X *		incl	include this path in list of dirs to recursively reap
X *		excl	leave out this directory and descendants
X *
X *	the functions are:
X *		include (text)	add text to incl list
X *		exclude (text)	add text to excl list
X *		preclude()	clear both lists
X */
struct filspec *
newnode(text)
char	*text;
{
X	struct filspec	*f;
X
X	if ( (f = (struct filspec *) malloc (sizeof(struct filspec))) == NULL ||
X	    (f->name = malloc (strlen(text)+1)) == NULL) {
X		fprintf (stderr, "%s: out of memory\n", progname);
X		exit (-1);
X	}
X	strcpy (f->name, text);
X	return (f);
}
Xexclude(text)
char		*text;
{
X	struct filspec	*f;
X
X	f = newnode(text);
X	f->next = excl;
X	excl = f;
}
include(text)
char	*text;
{
X	struct filspec	*f;
X
X	f = newnode(text);
X	f->next = incl;
X	incl = f;
}
preclude()
{
X	struct filspec	*p;
X
X	while (incl != NULL) {
X		p = incl;
X		incl = incl->next;
X		free (p);
X	}
X	while (excl != NULL) {
X		p = excl;
X		excl = excl->next;
X		free (p);
X	}
}
SHAR_EOF
chmod 0664 lib.c ||
echo 'restore of lib.c failed'
Wc_c="`wc -c < 'lib.c'`"
test 2539 -eq "$Wc_c" ||
	echo 'lib.c: original size 2539, current size' "$Wc_c"
fi
# ============= list.sample ==============
if test -f 'list.sample' -a X"$1" != X"-c"; then
	echo 'x - skipping list.sample (File already exists)'
else
echo 'x - extracting list.sample (Text)'
sed 's/^X//' << 'SHAR_EOF' > 'list.sample' &&
.1 junk,alt.flame,control,general
1 alt.sex.pictures
1 comp.os.vms
.5 talk
1 sci,!sci.skeptic
2 sci.skeptic
.5 misc.jobs,misc.handicap
2 soc
2 alt.tv
1 biz
.5 rec,!rec.arts.anime,!rec.arts.animation,!rec.arts.movies,!rec.games.hack,!rec.ham-radio,!rec.autos,!rec.arts.tv,!rec.audio,!rec.video,!rec.music
1 rec.music,!rec.music.synth
3 rec.music.synth
2 rec.arts.anime,rec.arts.animation,rec.arts.movies,rec.ham-radio,rec.autos,rec.arts.tv,rec.audio,rec.video
3 rec.games.hack
1 misc.headlines,misc.consumers,misc.kids,misc.legal
1 alt.sex,!alt.sex.bondage,!alt.sex.pictures
1 comp.sources
1 alt.sources
2 comp,!comp.os.vms
1 news
3 misc
3 pubnet
3 alt
3 unix-pc
# up to here is "normal expire"... now let's get desparate
.1 alt.sex.pictures
.1 rec.arts.anime,rec.arts.animation,rec.arts.movies,rec.ham-radio,rec.autos,rec.arts.tv,rec.audio,rec.video
.1 biz
.1 talk
.1 news
SHAR_EOF
chmod 0664 list.sample ||
echo 'restore of list.sample failed'
Wc_c="`wc -c < 'list.sample'`"
test 873 -eq "$Wc_c" ||
	echo 'list.sample: original size 873, current size' "$Wc_c"
fi
# ============= main.c ==============
if test -f 'main.c' -a X"$1" != X"-c"; then
	echo 'x - skipping main.c (File already exists)'
else
echo 'x - extracting main.c (Text)'
sed 's/^X//' << 'SHAR_EOF' > 'main.c' &&
X
/*
X * main.c
X *	main routine and globals for reap utility.
X */
X
X
/* directory containing news articles */
#define NEWSDIR		"/usr/spool/news"
X
X
#include "reap.h"
X
/* linked lists for included and excluded file specs */
struct filspec
X		*incl = NULL,
X		*excl = NULL
;
char
X		*scriptfile = "/usr/lib/news/reaplist",
X		*progname,
X		*newsdir = NEWSDIR
;
int
X		verbose = 0,	/* verbosity flag */
X		nodel = 0	/* do-not-unlink flag */
;
time_t
X		age,		/* unlink files older than this	*/
X		now		/* current time			*/
;
X
X
X
main(argc, argv)
int	argc;
char	*argv[];
{
X	int		device,		/* dev.no. of disk containing newsdir */
X			c,
X			errflag = 0;
X	long		wantblox;	/* desired free space */
X	char		*p;
X	static char	line[MAXLINE];
X	FILE		*script;	/* file ptr for function script */
X	struct stat	st;
X
X
/* parse args */
X	progname = argv[0];
X
X	while ( (c=getopt(argc,argv,"vnf:")) != EOF)
X		switch(c) {
X		    case 'n':
X			++nodel;
X			break;
X		    case 'v':
X			++verbose;
X			break;
X		    case 'f':
X			scriptfile = optarg;
X			break;
X		    case '?':
X			++errflag;
X			break;
X		}
X	
X	if (argc - optind != 1 ||
X	    sscanf(argv[optind], "%ld", &wantblox) != 1 )
X		++errflag;
X
X	if (errflag) {
X		fprintf (stderr,
X		    "usage: %s [-v] [-n] [-f funclist] freeblocks\n", progname);
X		exit (-1);
X	}
X
X
/* stat newsdir to find the number of the device it's on */
X	if (stat(newsdir, &st)) {
X		fprintf (stderr, "%s: can't stat %s\n", progname, newsdir);
X		exit (-1);
X	}
X	device = st.st_dev;
X
/* open function file for reading */
X	if ( (script = fopen (scriptfile, "r")) == NULL) {
X		fprintf (stderr, "%s: can't read %s\n", progname, scriptfile);
X		exit (-1);
X	}
X
/* when am i? */
X	time (&now);
X
/* chdir to newsdir */
X	chdir (newsdir);
X
/* main loop is right here ... process until goal reached or end of script */
X
X	while (freeblox(device) < wantblox &&
X	    (p = fgets (line, MAXLINE, script)) != NULL )
X		parse (line);
X
X	fclose (script);
X
/* return 0 if freespace goal was met at exit, 1 if not */
X	exit (freeblox(device) >= wantblox ? 0 : 1);
X
} /* main() */
X
SHAR_EOF
chmod 0664 main.c ||
echo 'restore of main.c failed'
Wc_c="`wc -c < 'main.c'`"
test 2028 -eq "$Wc_c" ||
	echo 'main.c: original size 2028, current size' "$Wc_c"
fi
# ============= reap.8 ==============
if test -f 'reap.8' -a X"$1" != X"-c"; then
	echo 'x - skipping reap.8 (File already exists)'
else
echo 'x - extracting reap.8 (Text)'
sed 's/^X//' << 'SHAR_EOF' > 'reap.8' &&
.TH REAP 8 LOCAL
.SH NAME
reap - delete news articles as space needed
.SH SYNOPSIS
.B reap
[-v] [-n] [-f funcfile] freeblocks
.SH DESCRIPTION
.I Reap
checks disk freespace and deletes articles according to a user-specified
scheme until
.I freeblocks
minimum freespace is available.
.PP
The
.B -n
causes a dry run (as in make(1)).  When this
option is specified, reap merely reports which files it would delete if
the option were left off.  This is useful for debugging function scripts.
However, since no space is actually freed in this mode,
it will either do nothing
(there was enough space to begin with) or proceed through the entire function
script file, indicating that reap would delete all applicable articles.
.PP
With the
.B
-v
option, reap prints a verbose commentary on its progress to the standard
output.
.PP
By default, reap looks in /usr/lib/news/reaplist for its list of functions.
The
.B -f
option is used to specify an alternate function script file.
.SH FUNCTION SCRIPT FILE FORMAT
A function script file consists of a series of expire functions, one per line.
Each expire function contains an age (in days, decimals okay), followed
by a list of newsgroups to be expired.
Blank lines and lines beginning with a "#" are ignored.
A sample function script file:
.PP
.nf
X	# function script file
X	0.5	talk,junk,alt.sex.pictures
X	2	rec,!rec.games,!rec.humor
X	3	misc
.fi
.PP
In the example,
if space is needed, reap
Xexpires to .5 days the entire talk and junk
hierarchies, and alt.sex.pictures.  If
still more space is needed,
rec hierarchy (excepting rec.games.* and rec.humor.*) is expired
to 2 days.  If freespace is still short, reap then expires all of misc
to 2 days.
.PP
Note that, while it would be possible to consolidate the second and
third lines, leaving them separate makes it posisble for reap to stop
in between them if sufficient space is cleared.
.SH FILES
.TP 25
/usr/spool/news
News spool directory
.TP 25
/usr/lib/news/reaplist
Default function list file
.SH DIAGNOSTICS
Returns zero if sufficient space was free at exit, nonzero otherwise.
.SH BUGS
The use of the ustat() system call is not very portable, and your humble
author isn't aware of its non-system-V equivalents.
.PP
Lines in the function file are limited to 512 characters.
.PP
What constitutes a disk "block" is implementation-dependent.
SHAR_EOF
chmod 0644 reap.8 ||
echo 'restore of reap.8 failed'
Wc_c="`wc -c < 'reap.8'`"
test 2335 -eq "$Wc_c" ||
	echo 'reap.8: original size 2335, current size' "$Wc_c"
fi
# ============= reap.c ==============
if test -f 'reap.c' -a X"$1" != X"-c"; then
	echo 'x - skipping reap.c (File already exists)'
else
echo 'x - extracting reap.c (Text)'
sed 's/^X//' << 'SHAR_EOF' > 'reap.c' &&
X
/*
X * reap.c
X *	contains the reap() function.
X *
X * Once the global linked lists incl and excl have been stuffed,
X * reap() actually scans the filesystem for files that meet the specs
X * and unlinks them if they are older than the age global variable.
X */
X
#include "reap.h"
X
X
reap()
{
X	struct filspec	*f;
X
X	for (f = incl; f != NULL; f = f->next) {
X		if (verbose)
X			printf ("scanning %s ...\n", f->name);
X		dodir (f->name);
X	}
X
} /* reap() */
X
X
X
dodir(name)
char	*name;
{
X	/* the following can be overwritten safely during recursion */
X	static struct filspec	*e;
X	static struct stat	st;
X	static struct dirent	*dp;
X	/* the following must be preserved during recursion */
X	char			*fullpath;
X	DIR			*dirp;
X
X
X	if ( (dirp = opendir(name)) == NULL) {
X		fprintf (stderr, "%s: can't read directory %s\n", progname,
X		    name);
X		return (-1);
X	}
X
X	while ( (dp = readdir(dirp)) != NULL) {
X
X	/* skip dot and dotdot */
X		if (!strcmp(dp->d_name, ".") || !strcmp(dp->d_name, ".."))
X			continue;
X
X	/* build the full pathname of each object */
X		if ( (fullpath =
X		    malloc(strlen(name)+strlen(dp->d_name)+2)) == NULL) {
X			fprintf (stderr, "%s: out of memory\n", progname);
X			exit (-1);
X		}
X		sprintf (fullpath, "%s/%s", name, dp->d_name);
X
X	/* check for excluded paths */
X		for (e = excl; e != NULL; e = e->next)
X			if (!strcmp (fullpath, e->name))
X				break;
X		if (e != NULL) {
X			free (fullpath);
X			continue;
X		}
X
X	/* try to stat the object */
X		if (stat(fullpath,&st)) {
X			fprintf (stderr, "%s: can't stat %s\n",
X			    progname, fullpath);
X			free (fullpath);
X			continue;
X		}
X
X	/* recurse if it's a directory */
X		if ( st.st_mode & S_IFDIR ) {
X			dodir (fullpath);
X			free (fullpath);
X			continue;
X		}
X
X	/* it's a file... leave it alone if it's new enough */
X		if (st.st_mtime > age) {
X			free (fullpath);
X			continue;
X		}
X
X		if (nodel) {
X			printf ("Would unlink %s\n", fullpath);
X			free (fullpath);
X			continue;
X		}
X		if (verbose)
X			printf ("Unlinking %s\n", fullpath);
X
X		if (unlink (fullpath) == -1)
X			fprintf (stderr,
X			    "%s: cannot unlink %s\n", progname, fullpath);
X
X		free (fullpath);
X
X	} /* while */
X	closedir (dirp);
X
} /* dodir() */
SHAR_EOF
chmod 0664 reap.c ||
echo 'restore of reap.c failed'
Wc_c="`wc -c < 'reap.c'`"
test 2150 -eq "$Wc_c" ||
	echo 'reap.c: original size 2150, current size' "$Wc_c"
fi
# ============= reap.h ==============
if test -f 'reap.h' -a X"$1" != X"-c"; then
	echo 'x - skipping reap.h (File already exists)'
else
echo 'x - extracting reap.h (Text)'
sed 's/^X//' << 'SHAR_EOF' > 'reap.h' &&
X
/*
X * reap.h
X *	header file for reap utility
X */
X
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <time.h>
#include <malloc.h>
#include <sys/stat.h>
#include <ustat.h>
#include <dirent.h>
X
#define MAXLINE		512		/* max len of line in script	*/
#define SECINDAY	(3600 * 24)	/* seconds in a day		*/
X
/* structure for linked lists of included and excluded file specs */
struct filspec {
X	char		*name;
X	struct filspec	*next;
};
X
Xextern struct filspec
X		*incl,
X		*excl
;
Xextern char
X		*progname,
X		*scriptfile,
X		*newsdir
;
Xextern int
X		verbose,
X		nodel,
X		optind
;
Xextern char	*optarg;
Xextern double	atof();
Xextern long	freeblox();
Xextern time_t
X		age,
X		now
;
SHAR_EOF
chmod 0664 reap.h ||
echo 'restore of reap.h failed'
Wc_c="`wc -c < 'reap.h'`"
test 676 -eq "$Wc_c" ||
	echo 'reap.h: original size 676, current size' "$Wc_c"
fi
exit 0
-- 
My friend Franky said he wanted to know something about computers that I
didn't know, so I suggested he learn COBOL.