[news.sysadmin] "fixactive" -- repair corrupt netnews active file

taylor@limbo.Intuitive.Com (Dave Taylor) (02/28/90)

One of the fun things about netnews software is that it's a very
fragile house of cards that can start acting strangely for any of
a large number of reasons.  Well, on "limbo", for some completely
inexplicable reason, the behaviour we're seeing is that the numbers
in the "active" file end up not reflecting the reality of the
articles available on the disk.

No idea why, but after running "expire" and having it completely
mangle my active file rather than rebuild it as promised, I wrote 
"fixactive", a simple program to compare the information on disk with 
the information in the active file, and if different, to rewrite the 
active file to be correct.

Usage of the program is pretty simple:

	fixactive

if you're root will scan through things and fix the active file if
there's anything wrong with it.  If you're not root, it will simply
scan and report any discrepencies between the disk and active file.

The other options modify this behaviour as follows:

   -a fn   	Use 'fn' as the active file
           	  (default is '/usr/lib/news/active')

   -h dir  	Use 'dir' as the netnews spool home
           	  (default is '/usr/spool/news')

   -n      	Show what you'd need to do, but don't do it

   -v      	Verbose: lots of output along the way

I recommend that you try it out by "fixactive -n". 

						-- Dave Taylor
Intuitive Systems
Mountain View, CA  94043

taylor@limbo.intuitive.com    or   {uunet!}{decwrl,apple}!limbo!taylor


-- Attachment: 

# This is a shell archive.  Remove anything before this line,
# then unpack it by saving it in a file and typing "sh file".
#
# Wrapped by Dave Taylor <taylor@limbo> on Wed Feb 28 00:52:04 1990
#
# This archive contains:
#	fixactive.c	README		
#
# Error checking via wc(1) will be performed.
# Error checking via sum(1) will be performed.

LANG=""; export LANG
PATH=/bin:/usr/bin:$PATH; export PATH

if sum -r </dev/null >/dev/null 2>&1
then
	sumopt='-r'
else
	sumopt=''
fi

echo x - fixactive.c
cat >fixactive.c <<'@EOF'
/**				fixactive.c			**/

/** Simple program to rebuild the active file to ensure that it 
    correctly points to the oldest and newest articles available
    in each of the groups on this computer.

	(C) Copyright 1990, Dave Taylor  <taylor@Intuitive.Com>
	(C) Copyright 1990, Intuitive Systems
	
	Permission is hereby given for anyone to distribute this
	software through any means, without cost to the end consumer,
	and such that the above copyright notice remains intact.
**/

#include <stdio.h>
#include <sys/types.h>
#include <ctype.h>
#include <dirent.h>

#define ACTIVE		"/usr/lib/news/active"
#define NEWSHOME	"/usr/spool/news"

#define MODERATED	'm'
#define UNMODERATED	'y'

#define MAXGROUPS	1000

#define NLEN		40
#define SLEN		80

#ifndef TRUE
#  define  TRUE		1
#  define  FALSE	0
#endif

#define plural(n)	( n == 1 ? "" : "s")

struct grouprec {
	char	name[NLEN];		/* newsgroup name */
	int	oldest,			/* oldest article */
		newest,			/* newest article */
	        moderated;		/*   moderated?   */
       } active[MAXGROUPS];

int    groups = 0,
       moderated = 0,
       verbose = FALSE;

char  active_file[SLEN], news_home[SLEN];
extern char *optarg;

void exit();
unsigned short getuid();
void perror();
char *strcpy();

main(argc, argv)
int argc;
char **argv;
{
	FILE *fd;
	char  buffer[SLEN], name[SLEN], modflag;
	int   i, j, oldest, newest, changed = FALSE, dont_fix = FALSE;
	int   bad_groups = 0, c;

	/** first pass initialization.. **/

	strcpy(active_file, ACTIVE);
	strcpy(news_home, NEWSHOME);

	/* set flags, if necessary */

	while ((c = getopt(argc, argv, "a:h:nv")) != EOF) {
	  switch (c) {
	     case 'a' : strcpy(active_file, optarg);	break;
	     case 'h' : strcpy(news_home, optarg);	break;
	     case 'n' : dont_fix = TRUE;		break;
	     case 'v' : verbose = TRUE;			break;
	     default  : usage();			exit(0);
	  }
	}

	/* check to see if we're root or not? */

	if (getuid() != 0) dont_fix = TRUE;

	/****  FIRST let's open the existing active file and build
	       a list of all the groups available on this computer.
	*****/

	if ((fd = fopen(active_file, "r")) == NULL) {
	  perror("fopen: can't open active file");
	  exit(1);
	}

	while (fgets(buffer, SLEN, fd) != NULL) {
	  for (j=0, i=0; buffer[i] != ' '; i++) 
	    name[j++] = buffer[i];
	  name[j] = '\0';
	  sscanf( (char *) buffer + i, "%d %d %c", 
	         &newest, &oldest, &modflag);
	
	  /* and copy into our data structure */

	  strcpy(active[groups].name, name);
	  active[groups].newest = newest;
	  active[groups].oldest = oldest;
	
	  if (modflag == MODERATED) {
	    moderated++;
	    active[groups].moderated = TRUE;
	  }
	  else 
	    active[groups].moderated = FALSE;
	
	  /* and verify... */
	
	  if (verbose)
	     printf("Group '%s', oldest = %d, newest = %d, moderated = %s\n",
		     active[groups].name, active[groups].oldest,
		     active[groups].newest, 
		     active[groups].moderated? "YES": "NO");
	
	   groups++;
	}

	(void) fclose(fd);

	if (verbose) printf("\n");

	printf("Read in %d groups total, of which %d are moderated.\n\n",
	       groups, moderated);

	/** now let's go through each group and recompute the newest
	    and oldest based on the actual information in the directory
	    for that group.
	**/

	for (i = 0; i < groups; i++) {

	  printf("\r%s                       ", active[i].name);  
	  fflush(stdout);

	  if (reset_values_for(i)) {
	     changed = TRUE;
	     bad_groups++;
	  }
	}

	if (bad_groups) {
	   printf("\r                             ");    /* clear line */
	   printf(
	      "\nFound %d group%s incorrectly marked in the active file\n\n",
	      bad_groups, plural(bad_groups));
	}
	else
	   printf("\r   .. no problems found ..               \n");

	/** And now let's rewrite the active file if there are any changes 
            that we've encountered...  **/

	if (changed) {
	  if (dont_fix) {
	    printf(
"Note: No changes made since you're not root. Please notify your local news\n");
	    printf(
"administrator about the discrepancy between the active file and the actual\n");
	    printf(
"articles stored on the disk.\n\n");
	  }
	  else { 
	    rewrite_active_file();
	    printf("done!\n");
	  }
	}
}

int
reset_values_for(index)
int index;
{
	/** Given group[index], open that particular directory and then
	    scan through, setting lowest and highest as appropriate.
	    If there are any changes, make them in the data structure
	    and return TRUE.  Otherwise, return FALSE
	**/

	DIR *dirp;
	struct dirent *dp;
	int    i, j, lowest = 9999999, highest = 0, entries_checked = 0;
	char   dirname[SLEN], directory_name[SLEN];

	/** first step is to get the right directory name... **/

	for (j=0, i=0; active[index].name[i] != '\0'; ) {
	  if (active[index].name[i] == '.') {
	    dirname[j++] = '/';
	    i++;
	  }
	  else
	    dirname[j++] = active[index].name[i++];
	}
	dirname[j] = '\0';
	
	/** now prepend the home directory for netnews... **/

	sprintf(directory_name, "%s/%s", news_home, dirname);

	/** and let's open this baby up! **/

	if ((dirp = opendir(directory_name)) == NULL) {
	  fprintf(stderr, "Couldn't open directory '%s' for reading?\n",
		  directory_name);
	  perror("opendir");
	  return(FALSE);
	}

	while ((dp = readdir(dirp)) != NULL) {
	  if (isdigit(dp->d_name[0])) {
	     i = atoi(dp->d_name);
	     if (i < lowest) lowest = i;
	     if (i > highest) highest = i;
	     entries_checked++;
	  }
	}

	(void) closedir(dirp);

	/** and now let's check the information out and proceed accordingly **/

	if (entries_checked == 0)	/* no articles at all! */
	  return(FALSE);

	if (active[index].oldest == lowest && active[index].newest == highest)
	  return(FALSE);

	if (active[index].oldest != lowest) {
	  printf("\r(reset oldest article for '%s' from %d to %d)\n",
		  active[index].name, active[index].oldest,
		  lowest);
	  active[index].oldest = lowest;
	}
	    
	if (active[index].newest != highest) {
	  printf("\r(reset newest article for '%s' from %d to %d)\n",
		  active[index].name, active[index].newest,
		  highest);
	  active[index].newest = highest;
	}

	return(TRUE);;
}

rewrite_active_file()
{
	/** open the active file for writing, and then replace all the
	    information therein with the information in our data structure.
	    Note the funky output format for digits:
		groupname 5-digit-integer 5-digit-integer modflag
	    where numeric values must be padded with leading zeroes,
	    and "modflag" is "m" if moderated, or "y" otherwise.
	**/

	FILE *fd;
	int   i;
	
	if ((fd = fopen(active_file, "w")) == NULL) {
	  fprintf(stderr, "I couldn't open the active file for writing!\n");
	  perror("fopen(ACTIVE)");
	  exit(1);
	}

	/** now line by line output the information required **/

	for (i=0 ; i < groups; i++)
	  fprintf(fd, "%s %.5d %.5d %c\n",
		  active[i].name, active[i].newest, active[i].oldest,
		  active[i].moderated? MODERATED : UNMODERATED);
	
	/** and we're done.  Close it and let's boogie! **/

	fclose(fd);
}

usage()
{
	/** who what when where and why and all that jazz **/

	printf("Usage: fixactive [flags]\n\n");
	printf("Where [flags] can be any of:\n");
	
	printf("   -a fn   \tUse 'fn' as the active file\n");
	printf("           \t  (default is '%s')\n", ACTIVE);
	printf("   -h dir  \tUse 'dir' as the netnews spool home\n");
	printf("           \t  (default is '%s')\n", NEWSHOME);
	printf("   -n      \tShow what you'd need to do, but don't do it\n");
	printf("   -v      \tVerbose: lots of output along the way\n\n");
}
@EOF
set `sum $sumopt <fixactive.c`; if test $1 -ne 6044
then
	echo ERROR: fixactive.c checksum is $1 should be 6044
fi
set `wc -lwc <fixactive.c`
if test $1$2$3 != 2989977502
then
	echo ERROR: wc results of fixactive.c are $* should be 298 997 7502
fi

chmod 644 fixactive.c

echo x - README
cat >README <<'@EOF'
		FIXACTIVE -- or more News Bugs From Hell

One of the fun things about netnews software is that it's a very
fragile house of cards that can start acting strangely for any of
a large number of reasons.  Well, on "limbo", for some completely
inexplicable reason, the behaviour we're seeing is that the numbers
in the "active" file end up not reflecting the reality of the
articles available on the disk.

For example, we might have articles 364 to 377 on disk, while the
active file would only reflect an available article range of 370-377
or similar.  

No idea why, but after running "expire" and having it completely
mangle my active file rather than rebuild it as promised, I wrote 
"fixactive", a simple program to compare the information on disk with 
the information in the active file, and if different, to rewrite the 
active file to be correct.  This is the program that is distributed in 
this shar file.

Usage is pretty simple:

	fixactive

if you're root will scan through things and fix the active file if
there's anything wrong with it.  If you're not root, it will simply
scan and report any discrepencies between the disk and active file.

The other options modify this behaviour as follows:

   -a fn   	Use 'fn' as the active file
           	  (default is '/usr/lib/news/active')

   -h dir  	Use 'dir' as the netnews spool home
           	  (default is '/usr/spool/news')

   -n      	Show what you'd need to do, but don't do it

   -v      	Verbose: lots of output along the way

I recommend that you try it out by "fixactive -n". 

Remember: it's going to scan through the directory of every available
newsgroup on your computer, so it'll take a few minutes to run through
'em all.  Give it time.  (on my HP 9000, with 541 available groups, it
takes approximately 2.5 minutes to complete)

If anyone has any ideas why the active file might get corrupted in the
fashion described, I would be most interested in hearing about it.  Also,
if anyone makes any interesting or useful additions or modifications to
this program, please do let me (and the net) know.

						-- Dave Taylor
Intuitive Systems
201 Ada Avenue, Suite 41
Mountain View, CA  94043
(415) 966-1151

taylor@limbo.intuitive.com    or   {uunet!}{decwrl,apple}!limbo!taylor

@EOF
set `sum $sumopt <README`; if test $1 -ne 41665
then
	echo ERROR: README checksum is $1 should be 41665
fi
set `wc -lwc <README`
if test $1$2$3 != 603702260
then
	echo ERROR: wc results of README are $* should be 60 370 2260
fi

chmod 644 README

exit 0