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