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 --------------------------