[net.sources] Release of uucp support tool uuls

ajs@hpfcla.UUCP (12/06/83)

#N:hpfcla:21800002:000:20936
hpfcla!ajs    Dec  5 11:51:00 1983

Subject:  release of uuls(1)

Here is the uucp support  program  you've been waiting  for.  Uuls helps
enormously with the  administration  of  /usr/spool/uucp,  as long as it
doesn't get too huge (in which case, even ls has trouble).  You may find
yourself using it several times a day if you have a busy system and like
to keep an eye on uucp activity.

We've been using the command here at HP for six months with no problems.
We've also run it on UCB4.1  (compile  with  "-DUCB").  We're putting it
into the  public  domain  with one  restriction:  You can't  sell it for
profit or as part of a profit-making package.

The rest of this article consists of the manual page (uuls.1) and source
code (uuls.c), separated by and followed by lines of dashes.  Sorry, you
have to unpack it by hand.  The  manual  page  starts  with a couple  of
lines that should let you run it through nroff -man, but it's  otherwise
a literal document.

Enjoy!

Alan Silverstein, Hewlett-Packard Fort Collins Systems Division, Colorado
ucbvax!hplabs!hpfcla!ajs, 303-226-3800 x3053, N 40 31'31" W 105 00'43"

-------------------------- uuls.1 --------------------------
.ta 0.3i 1.1i 1.9i 2.7i 3.5i 4.3i 5.1i 5.9i 6.7i 7.5i
.nf
	UULS(1)			HP EXPERIMENTAL			UULS(1)


	NAME
	   uuls -- list spooled uucp transactions grouped by transaction

	SYNOPSIS
	   uuls    [-m] [directories...]
	   uuls -s [-m] [directories...]
	   uuls -k [-m] [directories...]

	DESCRIPTION
	   This  command  lists the  contents of uucp spool  directories
	   (default "/usr/spool/uucp") with the files grouped into three
	   categories:

	   Transactions:
		Each  line  starts  with  a  transaction   filename  and
		includes the name of each local (same-directory) subfile
		referenced by the transaction file (see below), possibly
		followed  by the total size in bytes (-s) or Kbytes (-k)
		in  the  transaction  (see  below).  The  -m  (meanings)
		option  replaces the subfile names with nodename,  user,
		and commandline information (see below).

	   Orphans:
		All subfiles not referenced by any transaction file.

	   Others:
		All other files in the  directory  (all files not listed
		under one of the above categories).

	   Filenames  are  columnated so there may be more than one file
	   per line.  If a transaction has more subfiles than fit on one
	   line, it is followed by continuation lines which are indented
	   further.

	   The -s (size in bytes) and -k (Kbytes)  options cause uuls to
	   follow each  transaction in the  Transactions  section with a
	   total  size  for  all  stat-able,   sendable  files  in  that
	   transaction.  This  includes  "D.*"  files only, not "C.*" or
	   "X.*"  files,  nor  files  outside  the  directory  which are
	   indirectly  referenced  by "C.*"  files.  Sizes are either in
	   bytes  or  rounded  to  the  nearest   Kbyte  (1024   bytes),
	   respectively.

	   The -m  (meanings)  option  causes uuls to follow "C.*" files
	   (only) with "nodename!username  commandline" line(s), one per
	   "D*X*"  subfile,  instead  of  subfilename(s).  Nodename  and
	   username are truncated at eight characters and commandline at
	   38 characters.  See below for details.

	   Filenames  are  listed  in  alphabetical  order  within  each
	   section,  except that the first section is only sorted by the
	   transaction filename.  Every file in the directory except "."
	   and ".."  appears  exactly once in the entire list, unless -m
	   is used.

	DETAILS
	   Transaction  files are those  whose  names start with "C." or
	   "X.".  Subfilenames,  which  usually  start  with  "D.*", are
	   gleaned from transaction file lines, at most one per line, as
	   follows:

		C.*:  "S<junk><blank><hyphen><junk><blank><filename><end>"
		X.*:  "F<blank><filename><end>"

		where <junk> ::= <any chars except blank or null>
		and   <end>  ::= <blank>|<tab>|<newline>|<null>

	   Lines that don't begin with the appropriate character ('S' or
	   'F'), and subfilenames of "D.0", are ignored.

	   Orphan  files are those whose names start with "D." and which
	   are not referenced by any transaction files.

	   This algorithm  extracts from transaction  files the names of
	   all subfiles  which should exist in the spool  directory when
	   the  transaction is not being actively  processed.  It is not
	   unusual to see "missing subfiles" and "orphans" if you uuls a
	   spool directory while uucico, uucp, uux, or uuxqt is active.

	   "Meanings"  information  is gotten  by  reading  each  "D*X*"
	   subfile referenced by each "C.*" file.  Nodename!username  is
	   taken from the last line in the file which is of the form:

		"U<blank>[<username><blank>[<nodename><blank>[<junk...>]]]"

	   Fields must be missing; separators must be exactly one blank.
	   Likewise,  commandline  is taken  from  the last  line of the
	   form:

		"C<blank>[<commandline>]"

	DIAGNOSTICS
	   The program  writes an appropriate  message to standard error
	   if  it  has  any  problems  dealing  with  a  specified  file
	   (directory),  including failure to get heap space.  It always
	   returns zero as its exit value.

	   If a transaction file is unopenable (wrong  permissions or it
	   disappeared  while uuls was running), its name is preceded by
	   a "*" and the size of the  transaction is zero.  If a subfile
	   is missing (filename not found in the directory being listed)
	   or  unstatable  (if  required  for -s or  -k),  its  name  is
	   preceded by a "*" and it  contributes  zero bytes to the size
	   of the transaction.

	   If  -m  is  specified   and  a  "D*X*"  file  is  missing  or
	   unreadable, its name is given with a "*" prepended, as usual.

	AUTHOR
	   Alan Silverstein, Hewlett-Packard Fort Collins Systems Div, Colorado
	   hplabs!hpfcla!ajs, 303-226-3800 x3053, N 40 31'31" W 105 00'43"

	SEE ALSO
	   mail(1), uucp(1), uuto(1), uux(1), uuxqt(1), stat(2)
-------------------------- uuls.c --------------------------
static char Uni_id[] = "@(#)HP 20.1";
/* UNISRC_ID: @(#)HP uuls.c	20.1	83/12/02  */
/*
 * Copyright Hewlett-Packard Company, 1983.  Permission is given for the use,
 * modification, and distribution of this program and manual entry with one
 * exception:  It may not be sold for profit individually nor as part of any
 * software package, regardless of modifications.
 *
 * List files in a uucp spool directory grouped by transaction.
 * Compile with -DDEBUG for more output.
 * Compile with -DUCB if strchr(3s) or getopt(3) are missing.
 *
 * Possible enhancements:
 *	Tell name of each directory listed if argc > 2.
 * 	Leave off empty sections or say "none" if no files.
 *
 * Author:
 *   Alan Silverstein, Hewlett-Packard Fort Collins Systems Division, Colorado
 *   ucbvax!hplabs!hpfcla!ajs, 303-226-3800 x3053, N 40 31'31" W 105 00'43"
 */


#ifdef UCB
#define	strchr index
#endif

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/dir.h>


#define	PRINTERR(part1,part2) \
		fprintf (stderr, "%s: %s %s\n", myname, part1, part2);

#define	MISSING   '*'				/* precedes missing file */
#define	INDLINE	  2				/* indent filename lines */
#define	INDFILE	  2				/* before each filename	 */
#define	FILESIZE  (DIRSIZ + 1)			/* filename plus null	 */
#define	INDSUB	  (INDLINE + INDFILE + FILESIZE) /* indent subfile lines  */
#define	ENDFILES  71				/* past last file column */

#define	NUMSIZE	  8				/* digits to allow for	 */
#define	USERSIZE  8				/* longest username	 */
#define	NODESIZE  8				/* longest nodename	 */
#define	NODEUSER  (NODESIZE + 1 + USERSIZE)	/* node!user		 */
#define	CMDSIZE   38				/* longest command line	 */

#define	TRANSACTION	0			/* types for printfile() */
#define	ORPHAN		1
#define	OTHER		2

char	*myname;				/* how command invoked	*/
int	kflag = 0;				/* -k (kbytes) option	*/
int	mflag = 0;				/* -m (meanings) option	*/
int	sflag = 0;				/* -s (size) option	*/
int	stflag = 0;				/* stat() required	*/
int	nextcol;				/* next column to print	*/
char	*DEFAULT[] = { "/usr/spool/uucp", 0 };	/* default arg list	*/

char	*strchr();				/* library routine	*/


/****************************************************************/

main (argc, argv)
	int	argc;
	char	**argv;
{
	struct	stat	statbuf;		/* for return from stat() */
	extern	int	optind;			/* for getopt()		  */
	char	optchar;			/* for getopt()		  */
	char	*dirname;			/* current directory name */
	char	*start, *end;			/* heap start, end + 1	  */

	myname = *argv;

/*
 * Check arguments:
 */
	while ((optchar = getopt (argc, argv, "kms")) != EOF)
		if	(optchar == 'k')	kflag = 1;
		else if	(optchar == 'm')	mflag = 1;
		else if	(optchar == 's')	sflag = 1;
		else usage ();

	if (kflag && sflag)
		usage ();
	stflag = (kflag || sflag);		/* stats are required */
	argc -= optind;
	argv += optind;

	if (argc < 1)				/* use default arg list */
		argv = DEFAULT;

/*
 * Stat each argument and check if directory:
 */
	for ( ; (dirname = *argv); argv++) {

		if (stat (dirname, &statbuf) < 0) {
			PRINTERR ("can't stat", dirname);
			continue;
		}

		if ((statbuf.st_mode & S_IFMT) != S_IFDIR) {
			PRINTERR (dirname, "is not a directory");
			continue;
		}
/*
 * Read directory contents into heap, sort list of files, list each file, and
 * return heap space:
 */
		if (readdir (dirname, &start, &end))
			lsfiles (dirname, start, end);

		if (brk (start) == -1)
			PRINTERR ("warning: failed to release heap space", "");
	}
}


/****************************************************************
 * Tell correct usage:
 */

usage ()
{
	fprintf (stderr, "usage:\t%s    [-m] [directories...]\n", myname);
	fprintf (stderr,       "\t%s -s [-m] [directories...]\n", myname);
	fprintf (stderr,       "\t%s -k [-m] [directories...]\n", myname);
	exit (1);
}


/****************************************************************
 * Read filenames from directory into heap space, sort them, and
 * tack on a terminator:
 *
 * Return one if successful, else return zero.
 */

readdir (dirname, startp, endp)
	char	*dirname;			/* name of directory	*/
	char	**startp, **endp;		/* heap start, end + 1	*/
{
	FILE	*dirp;				/* open file pointer	*/
	struct	direct dirbuf;			/* one file entry	*/
	long	needbytes = sizeof (dirbuf);	/* bytes to read	*/
	long	gotbytes; 			/* actually read	*/
	char	*fname;				/* filename from dir	*/

	char	*sbrk();
	int	strcmp();

	*startp = 0;				/* means "not set"      */

/*
 * Open directory for reading:
 */
	if ((dirp = fopen (dirname, "r")) == NULL) {
		PRINTERR ("can't open", dirname);
		return (0);
	}
/*
 * Read each directory entry, increase heap size, and save filename:
 */
	while ((gotbytes = fread (&dirbuf, 1, needbytes, dirp)) == needbytes)
	{
		if (dirbuf.d_ino && dirbuf.d_name[0])	/* valid dir entry */
		{
			if ((strncmp (dirbuf.d_name, ".",  2) == 0) ||
			    (strncmp (dirbuf.d_name, "..", 3) == 0))
				continue;		/* ignore those files */

			if ((int) (fname = sbrk (FILESIZE)) == -1)
			{
				PRINTERR ("can't get heapspace to do", dirname);
				fclose (dirp);
				return (0);
			}
			if (*startp == 0)		/* first time only */
				*startp = fname;	/* save start of heap */

			strncpy (fname, dirbuf.d_name, DIRSIZ);
			fname [DIRSIZ] = 0;		/* final null */
#ifdef DEBUG
			printf ("%x: %s\n", fname, fname);
#endif
		}
	}
	fclose (dirp);

/*
 * Check if read failed:
 */
	if (gotbytes != 0) {
		PRINTERR ("read failed from", dirname);
		return (0);
	}
/*
 * Set end and sort files in heap:
 */
	*endp = sbrk (0);

	if (*endp > *startp)
		qsort (*startp, (*endp - *startp) / FILESIZE, FILESIZE, strcmp);

#ifdef DEBUG
	{	char *cp;
		printf ("-- sorted:\n");
		for (cp = *startp; cp < *endp; cp += FILESIZE)
			printf ("%x: %s\n", cp, cp);
	}
#endif
	return (1);
}


/****************************************************************
 * List files based on filenames sorted in heapspace, in three phases:
 *
 * 1: Search for transaction files ("C.*" and "X.*") and list them and
 *    the subfiles they reference, with optional stats and prints of
 *    file sizes and total;
 * 2: List orphans (remaining "D.*" files);
 * 3: List other files (any not already referenced).
 *
 * During phases 1 and 2, filenames are removed from the list as they're listed.
 */

lsfiles (dirname, start, end)
	char	*dirname;			/* directory being done	*/
	char	*start, *end;			/* heap start, end + 1	*/
{
	char	*transp, *subp;			/* transaction, subfile	*/
	char	transname[FILESIZE];		/* save trans filename	*/
	char	*subname;			/* subfile within trans	*/
	FILE	*filep;				/* open trans file	*/
	struct	stat	statbuf;		/* return from stat()	*/
	int	statval = 0;			/* return from stat()	*/
	long	transize;			/* bytes in transaction	*/
	long	totsize = 0;			/* total bytes		*/

	char	*getsubname();

/*
 * Go to directory to be listed:
 */
	if (chdir (dirname) < 0) {
		PRINTERR ("can't chdir to", dirname);
		return;
	}
/*
 * List transactions (main file first, then referenced files):
 */
	printf ("Transactions:\n");

	for (transp = start; transp < end; transp += FILESIZE)

	    if ((strncmp (transp, "C.", 2) == 0) ||
		(strncmp (transp, "X.", 2) == 0)  )
	    {
		strcpy (transname, transp);	/* save filename */
		*transp  = 0;
		nextcol  = 1;
		transize = 0;

/*
 * Open transaction file, then list it:
 */
		filep = fopen (transname, "r");

		printfile (TRANSACTION, (filep == NULL), transname);

		if (filep == NULL) {
			if (stflag)
				printsize (0);
			printf ("\n");
			nextcol = 1;
			continue;
		}
/*
 * Get subfilenames from transaction file, search for them in the sorted
 * list, stat them if needed, and either analyze them or just list them
 * (whether found or missing):
 */
		while (subname = getsubname (filep, transname[0]))
		{
			for (subp = start; subp < end; subp += FILESIZE)
				if (strcmp (subname, subp) == 0) {
					*subp = 0;
					break;
				}
			if (stflag &&
			   ((statval = stat (subname, &statbuf)) >= 0))
				transize += statbuf.st_size;

			if (mflag && (transname[0] == 'C'))
				printmeaning (subname);
			else
				printfile (TRANSACTION,
					((statval < 0) || (subp >= end)),
					subname);
		}
		fclose (filep);
		if (stflag)
			printsize (transize);
		printf ("\n");
		totsize += transize;
	    }
/*
 * Print transaction totals if needed:
 */
	if (stflag) {
		nextcol = 1;
		printfile (TRANSACTION, 0, "total");
		printsize (totsize);
		printf ("\n");
	}
/*
 * List orphaned subfiles:
 */
	printf ("Orphans:");
	nextcol = ENDFILES;			/* force newline */

	for (transp = start; transp < end; transp += FILESIZE) {
		if (strncmp (transp, "D.", 2) == 0) {
			printfile (ORPHAN, 0, transp);
			*transp = 0;
		}
	}
/*
 * List other files in columns:
 */
	printf ("\nOthers:");
	nextcol = ENDFILES;			/* force newline */

	for (transp = start; transp < end; transp += FILESIZE)
		if (*transp)
			printfile (OTHER, 0, transp);
	printf("\n");
}


/****************************************************************
 * Get next subfilename from a transaction file (never more than
 * DIRSIZ non-null chars):
 *
 * Returns *subname if found, or zero at the end of the trans file.
 *
 * Subfilenames come from transaction file lines, at most one per line:
 *
 *	C.*:  "S<junk><blank><hyphen><junk><blank><filename><end>"
 *	X.*:  "F<blank><filename><end>"
 *
 * where <junk> ::= <any chars except blank or null>
 *	 <end>  ::= <blank>|<tab>|<newline>|<null>
 *
 * Subfilenames of "D.0" are ignored.
 */

char *
getsubname (filep, mode)
	FILE	*filep;				/* open transaction file    */
	char	mode;				/* type of file: 'C' or 'X' */
{
static	char	line[BUFSIZ];			/* read from trans file	*/
	char	*cp;				/* for scanning line	*/
	char	*subname;			/* found in line	*/

/*
 * Read lines and check first letters:
 */
	while (fgets (line, BUFSIZ, filep) != NULL)
	{
#ifdef DEBUG
		printf ("\ngetsubname: %s\n", line);
#endif
		cp = line;

		if (*cp != ((mode == 'C') ? 'S' : 'F'))
			continue;

/*
 * For "C.*" files, skip past " -", if any:
 */
		if (mode == 'C') {
			while (*cp && strncmp (cp, " -", 2))
				cp++;
			if (*cp++ == 0)
				continue;
		}
/*
 * Skip past next blank and save start of subname:
 */
		while (*cp && (*cp != ' '))
			cp++;

		subname = ++cp;

/*
 * Skip subname, but not more than DIRSIZ, and mark end:
 */
		while ((cp < subname + DIRSIZ) &&
			*cp && (*cp != ' ') && (*cp != '\t') && (*cp != '\n'))
			cp++;
		*cp = 0;

/*
 * Return subname if valid:
 */
		if ((*subname == 0) || (strcmp (subname, "D.0") == 0))
			continue;

		return (subname);
	}
	return (0);
}


/****************************************************************
 * Print transaction meaning ("C" and "U" lines from "D*X*" subfiles):
 *
 * Non-"D*X*" files are ignored.  If a "D*X*" subfile is missing, the usual
 * missing-file format is used.  Otherwise, the last "C" and "U" lines in
 * each file are used to print one line.
 */

printmeaning (subname)
	char	*subname;			/* name of subfile	*/
{
	FILE	*subp;				/* open subfile		*/
	char	subline[BUFSIZ];		/* line from subfile	*/
	char	nodeuser[NODEUSER+1];		/* node!user + null	*/
	char	cmdline [CMDSIZE+1];		/* commandline + null	*/

/*
 * Check if execute file; if not, do nothing:
 */
	if (strchr (subname, 'X') == NULL)
		return;

/*
 * Open file; if fails, print as if missing:
 */
	if ((subp = freopen (subname, "r", stdin)) == NULL)
		printfile (TRANSACTION, 1, subname);

/*
 * Get data from subfile, then print it:
 */
	else {
		while (gets (subline) != NULL)
			getmeaning (subline, nodeuser, cmdline);

		if (nextcol > 1 + INDSUB) {
			printf ("\n%*s", INDSUB, "");
			nextcol = 1 + INDSUB;
		}
		printf ("%*s%-*s%*s%s", INDFILE, "", NODEUSER, nodeuser,
				        INDFILE, "", cmdline);

		nextcol += INDFILE + NODEUSER + INDFILE + strlen (cmdline);
		fclose (subp);
	}
}


/****************************************************************
 * Get transaction meaning information from a subfile line:
 *
 * Node!user is built from "U " lines, and commandline from "C " lines.
 * Other lines are ignored.  Assumes "U " lines are "healthy" (no multiple
 * blanks), but can handle missing or null names.
 */

getmeaning (subline, nodeuser, cmdline)
	char	*subline;			/* line to read		*/
	char	*nodeuser;			/* field to update	*/
	char	*cmdline;			/* field to update	*/
{
	char	*user = &subline[2];		/* start of username	*/
	char	*node = "";			/* default null		*/
	char	*cp;

	if (strncmp (subline, "U ", 2) == NULL) {

		if (cp = strchr (user, ' ')) {	/* nodename does follow */
			*cp  = 0;		/* put trailing null */
			node = cp + 1;		/* start of nodename */
			if (cp = strchr (node, ' '))
				*cp  = 0;	/* put trailing null */
		}
		nodeuser[0] = 0;
		strncat (nodeuser, node, NODESIZE);
		strcat  (nodeuser, "!");
		strncat (nodeuser, user, USERSIZE);
	}
	else if (strncmp (subline, "C ", 2) == NULL) {
		strncpy (cmdline, &subline[2], CMDSIZE);
		cmdline[CMDSIZE] = 0;		/* trailing null */
	}
}


/****************************************************************
 * Print one filename with proper formatting:
 */

printfile (type, missing, filename)
	int	type;			/* trans, orphan, other	*/
	int	missing;		/* if need to mark file	*/
	char	*filename;
{
	int	space;			/* leading spaces used	*/

/* 
 * New line if needed:
 * TRANSACTION secondary lines are pre-indented an extra amount.
 */
	if (nextcol + INDFILE + FILESIZE > ENDFILES) {
		space = (type == TRANSACTION) ? INDSUB: 0;
		printf ("\n%*s", space, "");
		nextcol = 1 + space;
	}
/*
 * Figure spacing and print filename, with missing flag if needed:
 */
	space = ((nextcol == 1) * INDLINE) + INDFILE;

	printf ("%*s%c%-*s",
		space - 1, "",
		(missing ? MISSING : ' '),
		FILESIZE, filename);

	nextcol += space + FILESIZE;
}


/****************************************************************
 * Print transaction size at end of line with proper formatting:
 */

printsize (size)
	long	size;				/* number of bytes */
{
	if (kflag)				/* round to kbytes */
		size = (size + 512) / 1024;

	if (nextcol > ENDFILES) {
		printf ("\n");
		nextcol = 1;
	}
	printf ("%*s%*d", ENDFILES - nextcol + 1, "", NUMSIZE, size);
}


#ifdef UCB

/****************************************************************
 * Analyze options:
 *
 * This primitive version of getopt(), written from scratch, is 
 * provided so the program is more portable.
 */

int	optind = 0;				/* which option	*/
int	optoff = 0;				/* which letter	*/

getopt (argc, argv, options)
	int	argc;				/* unmodified	*/
	char	**argv;				/* unmodified	*/
	char	*options;			/* legal list	*/
{
	char	letter;

	optind += (optind == 0);		/* skip first arg */

	while (optind < argc) {			/* there are more args */
		if (optoff == 0) {		/* now at start of arg */
			if (argv[optind][0] != '-')	/* not opt arg */
				return (EOF);
			else
				optoff++;	/* move to next char */
		}

		if (letter = argv[optind][optoff++])	/* not end of arg */
			return (strchr (options, letter) ? letter : '?');

		optind++;
		optoff = 0;
	}
	return (EOF);				/* no more arguments */
}
#endif
-------------------------- end of article --------------------------