[mod.sources] v06i101: Count unread news articles

sources-request@mirror.UUCP (08/11/86)

Submitted by: ihnp4!inuxd!gat
Mod.sources: Volume 6, Issue 101
Archive-name: newscnt

This program has been used here for several months and seems to be
stable.  I haven't had the opportunity to try it out on other UNIXes.
    Glen A. Taylor
    AT&T (Consumer Products Division)
    Indianapolis, IN  (317) 845-3709
    ihnp4!inuxd!gat

[  With Glen's permission, I added the code to use getopt rather than
   scanargs, and the "#ifdef BSD" code; I don't think I broke anything.
   Note that this program uses strtok() -- if it's not in your library,
   the mod.sources archives have two public-domain implementations of
   it and the other string functions.  I haven't tested the program as
   extensively as I might like to, because its utility to me is limited;
   we use notes here.  --r$ ]

#!/bin/sh
# 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 rs@mirror.UUCP on Sun Aug  3 19:56:49 EDT 1986
# Contents:  README Makefile newscnt.1 newscnt.c scanargs.c
 
echo x - README
sed 's/^XX//' > "README" <<'@//E*O*F README//'
XXHere is a little utility that I hope others will find as useful as I
XXhave.  Ever since I started to read the net, I have wanted a means to
XXdetermine how much news is waiting for me before I dive into it.  When
XXthe "wn" program came across net.sources a couple months ago, I thought
XXmy problem was solved.  Unfortunately, that program didn't do what I
XXwanted; I am not interested in knowing all of the unread news on the
XXsystem but only the news groups that I read. Consequently I decided to
XXwrite "newscnt."

XXNewscnt makes use of your .newsrc file and the system's
XX/usr/lib/news/active file to determine your unread news in much the
XXsame way that readnews or vnews does.  It looks at your "option" line
XXin .newsrc to determine the news groups that interest you and then
XXbuild the structures it needs to count your unread articles.  By
XXdefault, newscnt only returns a total, but with the "-a" option it will
XXgive you a breakdown by newsgroup.  A manual page is included that
XXdescribes all the bells and whistles.

XXNewscnt uses the "scanargs" argument parser that was distributed quite
XXsome time ago on this net.  I have included a copy in this distribution
XXfor those who don't have one.

XXAny bug reports, suggestions for improvement, other complaints or even
XXpraise should be sent to:
XX	Glen A. Taylor
XX	ihnp4!inuxd!gat
XX	AT&T (Consumer Products Division)
XX	P.O. Box 1008
XX	Indianapolis, IN 46206    (317) 845-3709
@//E*O*F README//
chmod u=rw,g=rw,o=rw README
 
echo x - Makefile
sed 's/^XX//' > "Makefile" <<'@//E*O*F Makefile//'
XX##  If on a BSD system, enable the next line.  Also, make arrangements to
XX##  get strtok(3) pulled in somehow.
XX#BSD	= -DBSD

XX##  If you want getopt, enable this line:
XXGETOPT	= -DUSE_GETOPT
XX##  You may have to have something like this line, if getopt isn't in libc.a
XX#GETOBJ	= -lgetopt
XX##  If no getopt, enable this line:
XX#SCAN	= scanargs.o

XXCFLAGS=$(BSD) $(GETOPT)

XXnewscnt: newscnt.o $(SCAN)
XX	cc -o newscnt newscnt.o $(SCAN) $(GETOBJ)
@//E*O*F Makefile//
chmod u=rw,g=rw,o=rw Makefile
 
echo x - newscnt.1
sed 's/^XX//' > "newscnt.1" <<'@//E*O*F newscnt.1//'
XX.TH NEWSCNT 1 LOCAL
XX.SH NAME
XXnewscnt \- count unread network news based on user's newsgroup
XXchoices
XX.SH SYNOPSIS
XX.B newscnt
XX[ 
XX.BR - { sta }
XX] [
XX.B -x
XX] [
XX.B -f 
XXalt_.newsrc ] [
XX.B -n 
XXnewsgrp ...]
XX.SH DESCRIPTION
XX.I Newscnt
XXprovides a count of unread network news.  By default, the user's .newsrc 
XXfile is examined to determine a list of newsgroups.  These newsgroups
XXare then matched against the list of articles that the user has read
XXand a count of available unread news articles is determined.  The
XXprogram writes to stdout either "No news." if there are no unread news
XXarticles, or "\f2nnn\fP news articles." to indicate the number of
XXunread articles.  Options
XXare provided for altering the format in which the count(s) are
XXreturned and the inputs the program works from.
XX.P
XXThe following options are supported:
XX.TP .5i
XX\f3-\fP{\f3sta\fP}
XXThese three options, only one of which may be specified at a time,
XXaffect the output format of the program.
XX.RS .5i
XX.TP .5i
XX\f3s\fP
XXThe \f2silent\fP option -- nothing is written to stdout.  Only the
XXprogram's normal return codes are provided, e.g. a \f31\fP if there is
XXno unread news, a \f30\fP if there is unread news, or a \f3-1\fP if
XXthere was an error in the processing.
XX.TP .5i
XX\f3t\fP
XXThe \f2terse\fP option -- only the total number of unread news
XXarticles (including zero if there are no unread news articles) is
XXwritten to stdout.  No other text is written.
XX.TP .5i
XX\f3a\fP
XXThe \f2all\fP option -- in addition to a report of the total number of
XXunread news articles, the program writes a line of the form
XX"\f2newsgroup\fP: \f2nnn\fP articles" for each newsgroup having one or
XXmore unread news articles.
XX.RE
XX.TP .5i
XX\f3-x\fP
XXThis flag causes the program to ignore the articles that the user has
XXalready read in each newsgroup and report the total number of articles
XXavailable in the newsgroup.  The effect is similar to the \f3-x\fP
XXflag on most news reading programs.
XX.TP .5i
XX\f3-f \f2alt_.newsrc\fP
XXWhen this option is specified, the file \f2alt_.newsrc\fP is used
XXinstead of the user's \f2$HOME/.newsrc\fP file.
XX.TP .5i
XX\f3-n \f2newsgrps\fP ...
XXWhen this option is specified, the \f2newsgrps\fP specified on the
XXcommand line are reported on.  By default, the program searches
XXthrough the specified or default .newsrc file for a line that begins
XXwith the word \f2options\fP.  This \f2options\fP line is parsed for the
XXlist of \f2newsgrps\fP following a \f3-n\fP argument.  Any
XX\f2newsgrp\fP, either from the command line or from the .newsrc file,
XXprefaced with an exclamation point will be specifically excluded from
XXthe computation.  This is useful for excluding "embedded" newsgroups.
XX(\f3NOTE\fP:  A \f2newsgrp\fP is taken as the "prefix" to a set of
XXnewsgroups and all newsgroups with that prefix are automatically
XXincluded in the count.)
XX.SH FILES
XX/usr/lib/news/active
XX.br
XX$HOME/.newsrc
XX.SH CAVEATS
XXThis program makes use of the /usr/lib/news/active file to determine
XXthe available news items and not the actual news directory's contents.
XXConsequently, its answers may be off slightly if the active file does not
XXaccurately track the available news articles.  If you are told
XXyou have unusually large amounts of unread news, use the -a option to
XXpinpoint the newsgroup(s) with unusual counts
XXand then check to see if there are unexpired
XXarticles causing an unusually large "range" in the active file.
XX.P
XXThe .newsrc format is interpreted according to the author's understanding of 
XXthat file's format.  Variations in how this file is interpreted may exist.
XX.SH AUTHOR
XX.nf
XXGlen A. Taylor
XXAT&T (Consumer Products Divsion)
XXMay 1986
XX.fi
@//E*O*F newscnt.1//
chmod u=rw,g=rw,o=rw newscnt.1
 
echo x - newscnt.c
sed 's/^XX//' > "newscnt.c" <<'@//E*O*F newscnt.c//'
XX/* newscnt -- a program to enumerate unread net.news articles     *
XX *                                                                *
XX *   Glen A. Taylor (AT&T-IS)         May 1986                    *
XX *                                                                *
XX * Added the code inside the BSD and USE_GETOPT #ifdef's	  *
XX *   Rich $alz (Mirror Systems)       June 1986                   *
XX *                                                                */


XXstatic char sccsid[] = "@(#)newscnt.c	1.2";

XX#define BSD
XX#define DEBUG

XX#include <stdio.h>
XX#ifdef BSD
XX#include <strings.h>
XXchar *strtok();
XX#else
XX#include <string.h>
XX#endif
XX#include <pwd.h>
XX#include <ctype.h>

XX#define FALSE	0
XX#define TRUE	1
XX#define SILENT  4
XX#define TERSE   2
XX#define ALL     1
XX#define ERROR   -1
XX#define NEWS    0
XX#define NONEWS  1
XX#define MAXLENG 512
XX#define MAXGRPS 128
XX#define ACTIVE  "/usr/lib/news/active"
XX#define NEWSRC  ".newsrc"
XX#ifdef BSD
XX#define USER_NAME "USER"
XX#else
XX#define USER_NAME	"LOGNAME"
XX#endif

XXstruct grp {
XX    char *name;   /* news group name */
XX    long  lowest; /* lowest article number */
XX    long  highest; /* highest article number */
XX    int   done; /* to indicate this element has been processed */
XX    struct grp *ptr; /* ptr. to next element */
XX};

XXtypedef struct grp *GRPTR;

XXchar *namevec[MAXGRPS]; /* array of newsgroup name strings */

XXmain(argc,argv)
XXint argc;
XXchar **argv;
XX{

XX    char *alt_newsrc,  **newsgrps, *malloc();
XX    int silent, terse, all, flags, fflag, nflag, xflag, nr_newsgrps;
XX    FILE *fpnewsrc, *fpactive;
XX    int count = 0;
XX    GRPTR listhead = NULL;

XX    /* Parse arguments and establish working parameters */
XX#ifdef	USE_GETOPT
XX    {
XX	char c;
XX	extern char *optarg;
XX	extern int optind;

XX	nr_newsgrps = silent = terse = all = xflag = fflag = nflag = flags = 0;
XX	while ((c = getopt(argc, argv, "staxf:n:")) != EOF)
XX	    switch (c) {
XX		default:
XXUsage:
XX		    fprintf(stderr,
XX		       "usage: %s [-{sta}] [-x] [-f alt_.newsrc] -n newsgrp...",
XX		       argv[0]);
XX		    exit(1);
XX		case 's': silent = 1; break;
XX		case 't': terse = 1; break;
XX		case 'a': all = 1; break;
XX		case 'x': xflag = 1; break;
XX		case 'f': fflag = 1; alt_newsrc = optarg; break;
XX		case 'n': nflag = 1; nr_newsgrps += addgrp(optarg); break;
XX	    }
XX	if (silent + terse + all > 1)
XX	    goto Usage;
XX	for (argv += optind; *argv; argv++)
XX	    nr_newsgrps += addgrp(argv);
XX    }
XX#else	/* !USE_GETOPT */
XX    if (!scanargs(argc,argv, "% sta%- x%- f%-alt_.newsrc!s n%-newsgrp!*s",
XX	 &flags, &xflag, &fflag, &alt_newsrc, &nflag, &nr_newsgrps, &newsgrps))
XX	 exit(1);
XX    silent = flags & SILENT;
XX    terse  = flags & TERSE;
XX    all    = flags & ALL;
XX#endif	/* USE_GETOPT */

XX    /* Open the .newsrc file */
XX    if (fflag) { /* use the file name the user supplied */
XX	fpnewsrc = fopen(alt_newsrc,"r");
XX    }
XX    else { /* determine the user's .newsrc file and open */
XX	{
XX	    char *fname[MAXLENG];
XX	    char *uname, *getenv();
XX	    struct passwd *passptr, *getpwnam();

XX	    uname = getenv(USER_NAME); /* get user's login name */
XX	    passptr = getpwnam(uname); /* get user's home dir */
XX	    /* construct .newsrc path */
XX	    strcpy(fname, passptr -> pw_dir);
XX	    strcat(fname, "/");
XX	    strcat(fname, NEWSRC);

XX	    fpnewsrc = fopen(fname,"r");
XX	}
XX    }

XX    /* Derive array of newsgroup names to search for */
XX    if (!nflag) getgrps(fpnewsrc, &nr_newsgrps, &newsgrps);

XX#ifdef DEBUG
XX    { int i;
XX      printf("Number of newsgroups = %d\n", nr_newsgrps);
XX      for (i = 0; i < nr_newsgrps; i++) printf("%s\n", newsgrps[i]);
XX    }
XX#endif

XX    if (nr_newsgrps == 0) exit (ERROR); /* can't do anything! */

XX    /* Open the ACTIVE newsgroup file */
XX    if ((fpactive = fopen(ACTIVE, "r"))==NULL)
XX	exit(ERROR); /* cannot proceed w/o ACTIVE file */

XX    /* Go through the ACTIVE file and make linked list of all 
XX       newsgroups matching elements of the "newsgrps" array   */
XX    {
XX	char gname[MAXLENG];   /* tmp to hold active group name */
XX	long low, high;  /* tmps to hold active group low & high numbers */
XX	int i, j;
XX	char *sp, junk[10];
XX	GRPTR current, next;

XX	while (fscanf(fpactive,"%s %ld %ld %s\n", gname, &high, &low, junk)
XX	    != EOF) { /* read the entire ACTIVE file */

XX	    /* Compare gname to every name in newsgrps */
XX	    for (i=0; i < nr_newsgrps; i++) {
XX		sp = newsgrps[i];
XX		j = strlen(sp);
XX		/* check for a group to be explicitly skipped */
XX		if (*sp == '!') { 
XX		    sp++;
XX		    if (strncmp(gname,sp,j-1) == 0) break;
XX		}
XX		/* otherwise check for a positive match */
XX		else if (strncmp(gname,sp,j) == 0) {
XX		/* got a matching group so allocate new list element */
XX		    next = (struct grp *) malloc(sizeof (struct grp));

XX		    if (!listhead) {/* first element */
XX			listhead = next;
XX			current = next;
XX		    }
XX		    else {
XX			current -> ptr = next; /* link on this element */
XX			current = next;
XX		    }
XX		    /* fill in the list element structure */
XX		    j = strlen(gname) + 1; /* get length of group name */
XX		    current -> name = strcpy(malloc(j), gname);
XX		    current -> lowest = low;
XX		    current -> highest = high;
XX		    current -> done = FALSE;
XX		    current -> ptr = NULL;

XX		    /* terminate inner loop -- proceed to next group */
XX		    break;
XX		}
XX	    }
XX	}
XX    }

XX#ifdef DEBUG
XX    {
XX	GRPTR lptr;
XX	printf("\nList of relevant active newsgroups:\n");
XX	lptr = listhead;
XX	while (lptr != NULL) {
XX	    printf("%s %d %d\n",lptr->name,lptr->highest,lptr->lowest);
XX	    lptr = lptr->ptr;
XX	}
XX    }
XX#endif

XX    /* Work down the linked list and compare to .newsrc entries */
XX    rewind(fpnewsrc);
XX    {
XX	GRPTR next;
XX	char line[MAXLENG], *gname, *range;
XX	int ignore, cnt;

XX	while(fgets(line, MAXLENG, fpnewsrc) != NULL){
XX	    gname = line; /* name starts in first char position */
XX	    range = line; /* find the "range" list */
XX	    while (*range != ':' && *range != '!' && *range != '\0')
XX		range++; /* : and ! are the two legal name delimiters */
XX	    ignore = *range == '!' ? TRUE : FALSE; /* do we ignore? */
XX	    *range++ = '\0'; /* delimit gname from range */

XX	    /* Search the list for this newsgroup */
XX	    next = listhead;
XX	    while (next != NULL) {
XX		if (strcmp(gname,next -> name) == 0) {/* Found it */
XX		    if (!ignore) {
XX		        cnt = ckrange(next, range, xflag);
XX			count = count + cnt;
XX			if (all && cnt) printf("%s: %d articles\n",gname,cnt);
XX		    }
XX		    next -> done = TRUE;
XX		    break;
XX		}
XX		next = next -> ptr;
XX	    }
XX	}
XX	/* go through list one more time and get those that aren't "done" */
XX	next = listhead;
XX	while (next != NULL) {
XX	    if (! next -> done) {
XX		cnt = next -> highest - next -> lowest;
XX		if (cnt < 0) cnt = 0;
XX		count = count + cnt;
XX		/* print the nonzero entries if "all" were requested */
XX		if (all && cnt) printf("%s: %d articles\n",next->name,cnt);
XX	    }
XX	    next = next -> ptr;
XX	}
XX    }

XX    /* Print final total */
XX    if (silent) exit( count? NEWS : NONEWS);
XX    if (terse) printf("%d\n", count);
XX    else if (count) printf("%d news articles.\n",count);
XX    else printf("No news.\n");
XX    exit(count? NEWS : NONEWS);
XX}

XX#ifdef	USE_GETOPT
XX/* addgrp -- add a group list to the namevec global array.
XX	     returns count of groups added.			*/
XXaddgrp(p)
XXchar *p;
XX{
XX    static char SEPARATORS[] = " \t\n,";
XX    static int cnt;
XX    char *ptr;
XX    int i;

XX    ptr = strtok(p, SEPARATORS);
XX    for (i = 0; ptr = strtok(NULL, SEPARATORS); i++)
XX	namevec[cnt++] = strcpy(malloc(strlen(ptr) + 1), ptr);
XX    return(i);
XX}
XX#endif	/* USE_GETOPT */


XX/* getgrps -- reads the .newsrc file, finds the "option -n ... " news
XX	      group list, and copies news groups into an array of
XX	      strings.                                                */
XXgetgrps(fp, num, names)
XXFILE *fp;
XXint *num;
XXchar ***names;
XX{
XX    char line[MAXLENG], *ptr;
XX    int optflag = FALSE, nflag = FALSE;

XX    *names = namevec;
XX    *num = 0;
XX    while (fgets(line,MAXLENG,fp) != NULL ) {
XX	if (!optflag && strncmp(line,"option",6) == 0) { 
XX	    /* found an option line */
XX	    optflag = TRUE;
XX	    nflag = FALSE;
XX	    ptr = strtok(line," \t\n");
XX	    ptr = strtok(NULL," \t\n"); /* skip "option" */
XX	}
XX	else if (optflag && isspace(line[0])) {
XX	    /* found a continuation line */
XX	    ptr = strtok(line," \t\n");
XX	}
XX	else {
XX	    optflag = FALSE;
XX	    nflag = FALSE;
XX	}

XX	/* Parse the options line */
XX	if (optflag) {
XX	    while (!nflag && ptr != NULL) {
XX		if (strcmp(ptr,"-n") == 0) {
XX		    nflag = TRUE;
XX		    ptr = strtok(NULL, " \t\n"); /* consume "-n" */
XX		    break;
XX		}
XX		ptr = strtok(NULL, " \t\n"); /* get first news group */
XX	    }
XX	    while (nflag && ptr != NULL) {
XX		/* copy this news group into "newsgrps" */
XX		namevec[*num] = strcpy(malloc(strlen(ptr)+1), ptr);
XX		*num += 1;
XX		ptr = strtok(NULL, " \t\n");
XX	    }
XX	}
XX    }
XX}

XX/* ckrange -- compare a range specification against a high count / low
XX	      count and return the number of unread articles          */
XXint
XXckrange(ptr, range, flag)
XXGRPTR ptr;
XXchar *range;
XXint flag;
XX{
XX    long low, high, nxthi, nxtlo;
XX    int i, extent, count;
XX    char *bitmap, *nextseg(), *rp;

XX#ifdef DEBUG
XX    printf("<*> ckrange: \n\tname -- %s\n\thigh = %d\n\tlow = %d\n",
XX	ptr->name, ptr->highest, ptr->lowest);
XX    printf("\trange -- %s\n", range);
XX#endif

XX    /* get low and high values for the newsgroup */
XX    low = ptr -> lowest;
XX    high = ptr -> highest;
XX    if (low > high) return (0); /* this one is screwy! */


XX    if (flag) { /* ignore range info */
XX	count = high - low;
XX	if (count < 0 ) count = 0;
XX	return (count);
XX    }

XX    /* set up a bitmap to do the computation */
XX    extent = high - low + 1;
XX    if (extent <= 0) return (0);
XX    bitmap = malloc(extent);
XX    if (bitmap == NULL) exit(ERROR);
XX    for (i=0; i < extent; i++) bitmap[i] = '*'; /* init. bitmap */

XX    /* set bits (bytes, actually) for each read article */
XX    rp = range;
XX    while ((rp = nextseg(&nxthi,&nxtlo,rp)) != NULL) {
XX#ifdef DEBUG
XX    printf("<*> nexthi = %d, nextlo = %d\nnext seg = `%s'\n",
XX	nxthi, nxtlo, rp);
XX#endif

XX	/* check that the ones read intersect the ones avail */
XX	if (nxthi < low || nxtlo > high) continue;

XX	/* adjust the range markers to the intersection */
XX	nxthi = (high > nxthi) ? nxthi : high;
XX	nxtlo = (low < nxtlo) ? nxtlo : low;

XX	/* mark the articles that were read */
XX	if (nxtlo == nxthi) bitmap[nxtlo-low] = ' ';
XX	else for (i=nxtlo-low; i <= nxthi-low; i++) bitmap[i] = ' ';
XX    }

XX    /* count the set bits & go home */
XX    count = 0;
XX    for (i=0; i < extent; i++) if (bitmap[i] == '*') count++;
XX    free(bitmap);
XX    return (count);
XX}

XXchar *
XXnextseg(high, low, str)
XXlong *high, *low;
XXchar *str;
XX{
XX    char *p1, *retval;
XX    long val = 0L;
XX    int hyphen = FALSE, more = TRUE;

XX    if (*str == '\0') return(NULL);
XX    p1 = str;
XX    while (more) {
XX	switch (*p1) {
XX	case '0':
XX	case '1':
XX	case '2':
XX	case '3':
XX	case '4':
XX	case '5':
XX	case '6':
XX	case '7':
XX	case '8':
XX	case '9':
XX		    val = val * 10 + (*p1 - '0');
XX		    break;

XX	case '-':
XX		    *low = val;
XX		    val = 0L;
XX		    hyphen = TRUE;
XX		    break;

XX	case ' ':
XX	case ',':
XX	case '\n':
XX		    more = FALSE;
XX		    retval = ++p1;
XX		    *high = val;
XX		    if (!hyphen) *low = val;
XX		    break;

XX	case '\0':
XX		    more = FALSE;
XX		    retval = NULL;
XX		    *high = val;
XX		    if (!hyphen) *low = val;
XX		    return (retval);
XX	}
XX	p1++;
XX    }
XX    return (retval);
XX}
@//E*O*F newscnt.c//
chmod u=rw,g=rw,o=rw newscnt.c
 
echo x - scanargs.c
sed 's/^XX//' > "scanargs.c" <<'@//E*O*F scanargs.c//'
XX/* 
XX * $Header: scanargs.c,v 1.3 83/05/22 02:21:32 thomas Exp $
XX * 		Version 7 compatible
XX * 	Argument scanner, scans argv style argument list.
XX * 
XX * 	Some stuff is a kludge because sscanf screws up
XX * 
XX * 	Gary Newman - 10/4/1979 - Ampex Corp. 
XX * 
XX * 	Modified by Spencer W. Thomas, Univ. of Utah, 5/81 to
XX * 	add args introduced by 	a flag, add qscanargs call,
XX * 	allow empty flags.
XX * 
XX * 	Compiling with QUICK defined generates 'qscanargs' ==
XX * 	scanargs w/o floating point support; avoids huge size
XX * 	of scanf.
XX * 
XX * 	If you make improvements we'd like to get them too.
XX * 	Jay Lepreau	lepreau@utah-20, decvax!harpo!utah-cs!lepreau
XX * 	Spencer Thomas	thomas@utah-20, decvax!harpo!utah-cs!thomas 
XX * 
XX *	(I know the code is ugly, but it just grew, you see ...)
XX * 
XX * Modified by:	Spencer W. Thomas
XX * 	Date:	Feb 25 1983
XX * 1. Fixed scanning of optional args.  Now args introduced by a flag
XX *    must follow the flag which introduces them and precede any other
XX *    flag argument.  It is still possible for a flag introduced
XX *    argument to be mistaken for a "bare" argument which occurs
XX *    earlier in the format string.  This implies that flags may not
XX *    be conditional upon other flags, and a message will be generated
XX *    if this is attempted.
XX * 
XX * 2. Usage message can be formatted by inserting newlines, tabs and
XX *    spaces into the format string.  This is especially useful for
XX *    long argument lists.
XX * 
XX * 3. Added n/N types for "numeric" args.  These args are scanned
XX *    using the C language conventions - a number starting 0x is
XX *    hexadecimal, a number starting with 0 is octal, otherwise it is
XX *    decimal.
XX */

XX#include <stdio.h>
XX#include <ctype.h>
XX#include <varargs.h>

XXtypedef char bool;
XX/* 
XX * An explicit assumption is made in this code that all pointers look
XX * alike, except possible char * pointers.
XX */
XXtypedef int *ptr;

XX#define YES 1
XX#define NO 0
XX#define ERROR(msg)  {fprintf(stderr, "msg\n"); goto error; }

XX/* 
XX * Storage allocation macros
XX */
XX#define NEW( type, cnt )	(type *) malloc( (cnt) * sizeof( type ) )
XX#define RENEW( type, ptr, cnt )	(type *) realloc( ptr, (cnt) * sizeof( type ) )

XXstatic char * prformat();
XXstatic bool isnum();

XX/* 
XX * Argument list is (argc, argv, format, ... )
XX */
XX#ifndef	QUICK
XXscanargs ( va_alist )
XX#else
XXqscanargs ( va_alist )
XX#endif
XXva_dcl
XX{
XX    int     argc;			/* Actual arguments */
XX    char  **argv;
XX    char   *format;
XX    va_list argl;

XX    register    check;			/* check counter to be sure all argvs
XX					   are processed */
XX    register char  *cp;
XX    register    cnt;
XX    int	    optarg = 0;			/* where optional args start */
XX    int	    nopt;
XX    char    tmpflg,			/* temp flag */
XX	    typchr;			/* type char from format string */
XX    char    c;
XX    bool  * arg_used;			/* array of flags */
XX    char  * malloc();
XX    ptr	    aptr;			/* pointer to return loc */

XX    bool    required;
XX    int	    excnt;			/* which flag is set */
XX    bool    exflag;			/* when set, one of a set of exclusive
XX					   flags is set */

XX    bool    list_of;			/* set if parsing off a list of args */
XX    bool    comma_list;			/* set if AT&T style multiple args */
XX    int	  * cnt_arg;			/* where to stuff list count */
XX    int	    list_cnt;			/* how many in list */
XX    /* These are used to build return lists */
XX    char ** strlist;
XX    int   * intlist;
XX    long  * longlist;
XX    float * fltlist;
XX    double *dbllist;

XX    char   *ncp;			/* remember cp during flag scanning */
XX#ifndef	QUICK
XX    char   *cntrl;			/* control string for scanf's */
XX    char    junk[2];			/* junk buffer for scanf's */

XX    cntrl = "% %1s";			/* control string initialization for
XX					   scanf's */
XX#endif

XX    va_start( argl );
XX    argc = va_arg( argl, int );
XX    argv = va_arg( argl, char ** );
XX    format = va_arg( argl, char * );

XX    arg_used = NEW( bool, argc );
XX    if (arg_used == NULL)
XX    {
XX	fprintf(stderr, "malloc failed in scanargs, exiting\n");
XX	exit(-1);
XX    }
XX    else
XX    {
XX	for (cnt=0; cnt<argc; cnt++)
XX	    arg_used[cnt] = NO;
XX    }

XX    check = 0;
XX    cp = format;
XX    /* 
XX     * Skip program name
XX     */
XX    while ( *cp != ' ' && *cp != '\t' && *cp != '\n' && *cp != '\0' )
XX	cp++;

XX    while (*cp)
XX    {
XX	required = NO;			/* reset per-arg flags */
XX	list_of = NO;
XX	comma_list = NO;
XX	list_cnt = 0;
XX	switch (*(cp++))
XX	{
XX	    default: 			/* all other chars */
XX		break;
XX	    case ' ':			/* separators */
XX	    case '\t':
XX	    case '\n':
XX		optarg = 0;		/* end of optional arg string */
XX		break;

XX	    case '!': 			/* required argument */
XX		required = YES;
XX	    case '%': 			/* not required argument */
XXreswitch:				/* after finding '*' or ',' */
XX		switch (typchr = *(cp++))
XX		{
XX		    case ',':		/* argument is AT&T list of things */
XX			comma_list = YES;
XX		    case '*':		/* argument is list of things */
XX			list_of = YES;
XX			list_cnt = 0;	/* none yet */
XX			cnt_arg = va_arg( argl, int *);	/* item count * here */
XX			goto reswitch;	/* try again */

XX		    case '$':		/* "rest" of argument list */
XX			while ( argc > 1 && !arg_used[argc-1] )
XX			    argc--;	/* find last used argument */
XX			*va_arg( argl, int * ) = argc;
XX			break;

XX		    case '-': 		/* argument is flag */
XX			if (optarg > 0)
XX			    ERROR(Format error: flag conditional on flag not allowed);

XX		    /* go back to label */
XX			ncp = cp-1;	/* remember */
XX			cp -= 3;
XX			for (excnt = exflag = 0
XX				; *cp != ' ' && !(*cp=='-' &&(cp[-1]=='!'||cp[-1]=='%'));
XX				(--cp, excnt++))
XX			{
XX			    for (cnt = optarg+1; cnt < argc; cnt++)
XX			    {
XX			    /* flags all start with - */
XX				if (*argv[cnt] == '-' && !arg_used[cnt] &&
XX					!isdigit(argv[cnt][1]))
XX				    if (*(argv[cnt] + 1) == *cp)
XX				    {
XX					if (*(argv[cnt] + 2) != 0)
XX					    ERROR (extra flags ignored);
XX					if (exflag)
XX					    ERROR (more than one exclusive flag chosen);
XX					exflag++;
XX					required = NO;
XX					check += cnt;
XX					arg_used[cnt] = 1;
XX					nopt = cnt;
XX					*va_arg( argl, int *) |= (1 << excnt);
XX					break;
XX				    }
XX			    }
XX			}
XX			if (required)
XX			    ERROR (flag argument missing);
XX			cp = ncp;
XX			/* 
XX			 * If none of these flags were found, skip any
XX			 * optional arguments (in the varargs list, too).
XX			 */
XX			if (!exflag)
XX			{
XX			    va_arg( argl, int * );	/* skip the arg, too */
XX			    while (*++cp && ! isspace(*cp))
XX				if (*cp == '!' || *cp == '%')
XX				{
XX				    if ( *++cp == '*' || *cp == ',' )
XX				    {
XX					cp++;
XX					va_arg( argl, int * );
XX				    }
XX				    /* 
XX				     * Assume that char * might be a
XX				     * different size, but that all
XX				     * other pointers are same size.
XX				     */
XX				    if ( *cp == 's' )
XX					va_arg( argl, char * );
XX				    else
XX					va_arg( argl, ptr );
XX				}
XX			}
XX			else
XX			{
XX			    optarg = nopt;
XX			    cp++;	/* skip over - */
XX			}

XX			break;

XX		    case 's': 		/* char string */
XX		    case 'd': 		/* decimal # */
XX		    case 'o': 		/* octal # */
XX		    case 'x': 		/* hexadecimal # */
XX		    case 'n':		/* "number" in C syntax */
XX#ifndef	QUICK
XX		    case 'f': 		/* floating # */
XX#endif
XX		    case 'D': 		/* long decimal # */
XX		    case 'O': 		/* long octal # */
XX		    case 'X': 		/* long hexadecimal # */
XX		    case 'N':		/* long number in C syntax */
XX#ifndef	QUICK
XX		    case 'F': 		/* double precision floating # */
XX#endif
XX			for (cnt = optarg+1; cnt < argc; cnt++)
XX			{
XX			    ncp = argv[cnt];

XX			    if ( isnum( ncp, typchr, comma_list ) )
XX			    {
XX				if ( typchr == 's' )	/* string? */
XX				    continue;	/* don't want numbers, then */
XX			    }
XX			    else if ( *ncp == '-' )
XX				if ( optarg > 0 ) /* end optional args? */
XX				{
XX				    /* Eat the arg, too, if necessary */
XX				    if ( list_cnt == 0 )
XX					if ( typchr == 's' )
XX					    va_arg( argl, char * );
XX					else
XX					    va_arg( argl, ptr );
XX				    break;
XX				}
XX				else
XX				    continue;
XX			    else if ( typchr != 's' )
XX				continue;	/* not number, keep looking */
XX			    
XX			    /* 
XX			     * Otherwise usable argument may already
XX			     * be used.  (Must check this after
XX			     * checking for flag, though.)
XX			     */
XX			    if (arg_used[cnt]) continue;

XX			    /* 
XX			     * If it's a comma-and-or-space-separated
XX			     * list then count how many, and separate
XX			     * the list into an array of strings.
XX			     */
XX			    if ( comma_list )
XX			    {
XX				register char * s;
XX				int pass;

XX				/* 
XX				 * On pass 0, just count them.  On
XX				 * pass 1, null terminate each string 
XX				 */
XX				for ( pass = 0; pass <= 1; pass++ )
XX				{
XX				    for ( s = ncp; *s != '\0'; )
XX				    {
XX					if ( pass )
XX					    strlist[list_cnt] = s;
XX					while ( (c = *s) != '\0' && c != ' ' &&
XX						c != '\t' && c != ',' )
XX					    s++;
XX					if ( pass )
XX					    *s = '\0';

XX					list_cnt++;	/* count separators */
XX					/* 
XX					 * Two commas in a row give a null
XX					 * string, but two spaces
XX					 * don't.  Also skip spaces
XX					 * after a comma.
XX					 */
XX					if ( c != '\0' )
XX					    while ( *++s == ' ' || *s == '\t' )
XX						;
XX				    }
XX				    if ( pass == 0 )
XX				    {
XX					strlist = NEW( char *, list_cnt );
XX					list_cnt = 0;
XX				    }
XX				}
XX			    }
XX			    else if ( list_of )
XX				list_cnt++;   /* getting them one at a time */
XX			    /* 
XX			     * If it's either type of list, then alloc
XX			     * storage space for the returned values
XX			     * (except that comma-separated string
XX			     * lists already are done).
XX			     */
XX			    if ( list_of )
XX			    {
XX				if ( list_cnt == 1 || comma_list )
XX				    switch( typchr )
XX				    {
XX					case 's':
XX					    if ( !comma_list )
XX						strlist = NEW( char *, 1 );
XX					    aptr = (ptr) &strlist[0];
XX					    break;
XX					case 'n':
XX					case 'd':
XX					case 'o':
XX					case 'x':
XX					    intlist = NEW( int, list_cnt );
XX					    aptr = (ptr) &intlist[0];
XX					    break;
XX					case 'N':
XX					case 'D':
XX					case 'O':
XX					case 'X':
XX					    longlist = NEW( long, list_cnt );
XX					    aptr = (ptr) &longlist[0];
XX					    break;
XX					case 'f':
XX					    fltlist = NEW( float, list_cnt );
XX					    aptr = (ptr) &fltlist[0];
XX					    break;
XX					case 'F':
XX					    dbllist = NEW( double, list_cnt );
XX					    aptr = (ptr) &dbllist[0];
XX					    break;
XX				    }
XX				else
XX				    switch( typchr )
XX				    {
XX					case 's':
XX					    strlist = RENEW( char *, strlist,
XX							     list_cnt );
XX					    aptr = (ptr) &strlist[list_cnt-1];
XX					    break;
XX					case 'n':
XX					case 'd':
XX					case 'o':
XX					case 'x':
XX					    intlist = RENEW( int, intlist,
XX							     list_cnt );
XX					    aptr = (ptr) &intlist[list_cnt-1];
XX					    break;
XX					case 'N':
XX					case 'D':
XX					case 'O':
XX					case 'X':
XX					    longlist = RENEW( long, longlist,
XX							      list_cnt );
XX					    aptr = (ptr) &longlist[list_cnt-1];
XX					    break;
XX					case 'f':
XX					    fltlist = RENEW( float, fltlist,
XX							     list_cnt );
XX					    aptr = (ptr) &fltlist[list_cnt-1];
XX					    break;
XX					case 'F':
XX					    dbllist = RENEW( double, dbllist,
XX							     list_cnt );
XX					    aptr = (ptr) &dbllist[list_cnt-1];
XX					    break;
XX				    }
XX			    }
XX			    else
XX				aptr = va_arg( argl, ptr );

XX			    if ( typchr == 's' )
XX			    {
XX				if ( ! comma_list )
XX				    *(char **)aptr = ncp;
XX			    }
XX			    else
XX			    {
XX				nopt = 0;
XX				do {
XX				    /* 
XX				     * Need to update aptr if parsing
XX				     * a comma list
XX				     */
XX				    if ( comma_list && nopt > 0 )
XX				    {
XX					ncp = strlist[nopt];
XX					switch( typchr )
XX					{
XX					    case 'n':
XX					    case 'd':
XX					    case 'o':
XX					    case 'x':
XX						aptr = (ptr) &intlist[nopt];
XX						break;
XX					    case 'N':
XX					    case 'D':
XX					    case 'O':
XX					    case 'X':
XX						aptr = (ptr) &longlist[nopt];
XX						break;
XX					    case 'f':
XX						aptr = (ptr) &fltlist[nopt];
XX						break;
XX					    case 'F':
XX						aptr = (ptr) &dbllist[nopt];
XX						break;
XX					}
XX				    }
XX				    /* 
XX				     * Do conversion for n and N types
XX				     */
XX				    tmpflg = typchr;
XX				    if (typchr == 'n' || typchr == 'N' )
XX					if (*ncp != '0')
XX					    tmpflg = 'd';
XX					else if (*(ncp+1) == 'x' ||
XX						 *(ncp+1) == 'X')
XX					{
XX					    tmpflg = 'x';
XX					    ncp += 2;
XX					}
XX					else
XX					    tmpflg = 'o';
XX				    if (typchr == 'N')
XX					toupper( tmpflg );


XX#ifndef	QUICK
XX				    cntrl[1] = tmpflg;/* put in conversion */
XX				    if (sscanf (ncp, cntrl, aptr, junk) != 1)
XX					ERROR (Bad numeric argument);
XX#else
XX				    if (numcvt(ncp, tmpflg, aptr) != 1)
XX					ERROR (Bad numeric argument);
XX#endif
XX				} while ( comma_list && ++nopt < list_cnt );
XX			    }
XX			    check += cnt;
XX			    arg_used[cnt] = 1;
XX			    required = NO;
XX			    /*
XX			     * If not looking for multiple args,
XX			     * then done, otherwise, keep looking.
XX			     */
XX			    if ( !( list_of && !comma_list ) )
XX				break;
XX			    else
XX				continue;
XX			}
XX			if (required)
XX			    switch (typchr)
XX			    {
XX				case 'x': 
XX				case 'X': 
XX				    ERROR (missing hexadecimal argument);
XX				case 's': 
XX				    ERROR (missing string argument);
XX				case 'o': 
XX				case 'O': 
XX				    ERROR (missing octal argument);
XX				case 'd': 
XX				case 'D': 
XX				    ERROR (missing decimal argument);
XX				case 'f': 
XX				case 'F': 
XX				    ERROR (missing floating argument);
XX				case 'n':
XX				case 'N':
XX				    ERROR (missing numeric argument);
XX			    }
XX			if ( list_cnt > 0 )
XX			{
XX			    *cnt_arg = list_cnt;
XX			    switch ( typchr )
XX			    {
XX				case 's':
XX				    *va_arg( argl, char *** ) = strlist;
XX				    break;
XX				case 'n':
XX				case 'd':
XX				case 'o':
XX				case 'x':
XX				    *va_arg( argl, int ** ) = intlist;
XX				    break;
XX				case 'N':
XX				case 'D':
XX				case 'O':
XX				case 'X':
XX				    *va_arg( argl, long ** ) = longlist;
XX				    break;
XX				case 'f':
XX				    *va_arg( argl, float ** ) = fltlist;
XX				    break;
XX				case 'F':
XX				    *va_arg( argl, double **) = dbllist;
XX				    break;
XX			    }
XX			    if ( typchr != 's' )
XX				free( (char *) strlist );
XX			}
XX			else if ( cnt >= argc )
XX			{
XX			    /* Fell off end looking, so must eat the arg */
XX			    if ( typchr == 's' )
XX				va_arg( argl, char * );
XX			    else
XX				va_arg( argl, ptr );
XX			}
XX			break;
XX		    default: 		/* error */
XX			fprintf (stderr, "error in call to scanargs\n");
XX			return (0);
XX		}
XX	}
XX    }

XX    /*  Count up empty flags */
XX    for (cnt=1; cnt<argc; cnt++)
XX	if (argv[cnt][0] == '-' && argv[cnt][1] == '-' && argv[cnt][2] == 0
XX	    && !arg_used[cnt] )
XX	    check += cnt;

XX    /* sum from 1 to N = n*(n+1)/2 used to count up checks */
XX    if (check != (((argc - 1) * argc) / 2))
XX	ERROR (extra arguments not processed);

XX    free(arg_used);
XX    return (1);

XXerror: 
XX    fprintf (stderr, "usage : ");
XX    if (*(cp = format) != ' ')
XX    {
XX	if ( *cp == '%' )
XX	{
XX	    /* 
XX	     * This is bogus, but until everyone can agree on a name
XX	     * for (rindex/strrchr) ....
XX	     */
XX	    for ( cp = argv[0]; *cp != '\0'; cp++ )
XX		;			/* find the end of the string */
XX	    for ( ; cp > argv[0] && *cp != '/'; cp-- )
XX		;			/* find the last / */
XX	    if ( *cp == '/' )
XX		cp++;
XX	    fprintf( stderr, "%s", cp );

XX	    cp = format + 1;		/* reset to where it should be */
XX	}
XX	while (putc (*cp++, stderr) != ' ');
XX    }
XX    else
XX	fprintf (stderr, "?? ");
XX    while (*cp == ' ')
XX	cp++;
XX    prformat (cp, NO);
XX    free(arg_used);
XX    return 0;
XX}

XXstatic char *
XXprformat (format, recurse)
XXchar   *format;
XX{
XX    register char  *cp;
XX    bool    required, comma_list;
XX    int    list_of;

XX    cp = format;
XX    if (recurse)
XX	putc (' ', stderr);

XX    required = NO;
XX    list_of = 0;
XX    comma_list = NO;
XX    while (*cp)
XX    {
XX	switch (*cp)
XX	{
XX	    default: 
XX		cp++;
XX		break;
XX	    case ' ':
XX	    case '\t':
XX	    case '\n':
XX		putc(*cp, stderr);
XX		format = ++cp;
XX		break;
XX	    case '!': 
XX		required = YES;
XX	    case '%': 
XXreswitch:
XX		switch (*++cp)
XX		{
XX		    case ',':
XX			comma_list++;
XX		    case '*':
XX			list_of++;
XX			goto reswitch;

XX		    case '$':		/* "rest" of argument list */
XX			if (!required)
XX			    putc ('[', stderr);
XX			for (; format < cp - 1 - list_of; format++)
XX			    putc (*format, stderr);
XX			fputs( " ...", stderr );
XX			if ( !required )
XX			    putc( ']', stderr );
XX			break;

XX		    case '-': 		/* flags */
XX			if (!required)
XX			    putc ('[', stderr);
XX			putc ('-', stderr);

XX			if (cp - format > 2 + list_of)
XX			    putc ('{', stderr);
XX			cp = format;
XX			while (*cp != '%' && *cp != '!')
XX			    putc (*cp++, stderr);
XX			if (cp - format > 1 + list_of)
XX			    putc ('}', stderr);
XX			cp += 2;	/* skip !- or %- */
XX			if (*cp && !isspace(*cp))
XX			    cp = prformat (cp, YES);
XX					/* this is a recursive call */
XX			if (!required)
XX			    putc (']', stderr);
XX			break;
XX		    case 's': 		/* char string */
XX		    case 'd': 		/* decimal # */
XX		    case 'o': 		/* octal # */
XX		    case 'x': 		/* hexadecimal # */
XX		    case 'f': 		/* floating # */
XX		    case 'D': 		/* long decimal # */
XX		    case 'O': 		/* long octal # */
XX		    case 'X': 		/* long hexadecimal # */
XX		    case 'F': 		/* double precision floating # */
XX		    case 'n':		/* numeric arg (C format) */
XX		    case 'N':		/* long numeric arg */
XX			if (!required)
XX			    putc ('[', stderr);
XX			for (; format < cp - 1 - list_of; format++)
XX			    putc (*format, stderr);
XX			if ( list_of != 0 )
XX			{
XX			    if ( comma_list )
XX				putc( ',', stderr );
XX			    else
XX				putc( ' ', stderr );
XX			    fputs( "...", stderr );
XX			}
XX			if (!required)
XX			    putc (']', stderr);
XX			break;
XX		    default: 
XX			break;
XX		}
XX		required = NO;
XX		list_of = NO;
XX		comma_list = NO;
XX		if (*cp)		/* check for end of string */
XX		    format = ++cp;
XX		if (*cp && !isspace(*cp))
XX		    putc (' ', stderr);
XX	}
XX	if (recurse && isspace(*cp))
XX	    break;
XX    }
XX    if (!recurse)
XX	putc ('\n', stderr);
XX    return (cp);
XX}

XX/* 
XX * isnum - determine whether a string MIGHT represent a number.
XX * typchr indicates the type of argument we are looking for, and
XX * determines the legal character set.  If comma_list is YES, then
XX * space and comma are also legal characters.
XX */
XXstatic bool
XXisnum( str, typchr, comma_list )
XXregister char * str;
XXchar typchr;
XXbool comma_list;
XX{
XX    register char * allowed, * digits, * cp;
XX    bool hasdigit = NO;

XX    switch( typchr )
XX    {
XX	case 'n':
XX	case 'N':
XX	    allowed = " \t,+-x0123456789abcdefABCDEF";
XX	    break;
XX	case 'd':
XX	case 'D':
XX	    allowed = " \t,+-0123456789";
XX	    break;
XX	case 'o':
XX	case 'O':
XX	    allowed = " \t,01234567";
XX	    break;
XX	case 'x':
XX	case 'X':
XX	    allowed = " \t,0123456789abcdefABCDEF";
XX	    break;
XX	case 'f':
XX	case 'F':
XX	    allowed = " \t,+-eE.0123456789";
XX	    break;
XX	case 's':			/* only throw out decimal numbers */
XX	default:
XX	    allowed = " \t,+-.0123456789";
XX	    break;
XX    }
XX    digits = allowed;
XX    while ( *digits != '0' )
XX	digits++;
XX    if ( ! comma_list )
XX	allowed += 3;		      /* then don't allow space, tab, comma */

XX    while ( *str != '\0' )
XX    {
XX    	for ( cp = allowed; *cp != '\0' && *cp != *str; cp++ )
XX    	    ;
XX    	if ( *cp == '\0' )
XX	    return NO;		     /* if not in allowed chars, not number */
XX	if ( cp - digits >= 0 )
XX	    hasdigit = YES;
XX	str++;
XX    }
XX    return hasdigit;
XX}

XX#ifdef	QUICK
XXnumcvt(str, conv, val)
XXregister char *str;
XXchar conv;
XXint *val;
XX{
XX    int base, neg = 0;
XX    register unsigned int d;
XX    long retval = 0;
XX    register char *digits;
XX    extern char *index();
XX    if (conv == 'o' || conv == 'O')
XX	base = 8;
XX    else if (conv == 'd' || conv == 'D')
XX	base = 10;
XX    else if (conv == 'x' || conv == 'X')
XX	base = 16;
XX    else
XX	return 0;

XX    if (*str == '-')
XX    {
XX	neg = 1;
XX	str++;
XX    }
XX    while (*str)
XX    {
XX	if (*str >= '0' && *str < '0'+base)
XX	    d = *str - '0';
XX	else if (base == 16 && *str >= 'a' && *str <= 'f')
XX	    d = 10 + *str - 'a';
XX	else if (base == 16 && *str >= 'A' && *str <= 'F')
XX	    d = 10 + *str - 'A';
XX	else
XX	    return 0;
XX	retval = retval*base + d;
XX	str++;
XX    }
XX    if (neg)
XX	retval = -retval;
XX    if (conv == 'D' || conv == 'O' || conv == 'X')
XX	*(long *) val = retval;
XX    else
XX	*val = (int) retval;
XX    return 1;
XX}
XX#endif	QUICK
@//E*O*F scanargs.c//
chmod u=rw,g=rw,o=rw scanargs.c
 
echo Inspecting for damage in transit...
temp=/tmp/sharin$$; dtemp=/tmp/sharout$$
trap "rm -f $temp $dtemp; exit" 0 1 2 3 15
cat > $temp <<\!!!
      29     244    1409 README
      15      73     436 Makefile
      96     593    3593 newscnt.1
     437    1727   11200 newscnt.c
     833    3103   19707 scanargs.c
    1410    5740   36345 total
!!!
wc  README Makefile newscnt.1 newscnt.c scanargs.c | sed 's=[^ ]*/==' | diff -b $temp - >$dtemp
if test -s $dtemp
then echo "Ouch [diff of wc output]:" ; cat $dtemp
else echo "No problems found."
fi
exit 0