[comp.sources.misc] v14i088: enhanced du

chip@chinacat.Unicom.COM (Chip Rosenthal) (09/16/90)

Posting-number: Volume 14, Issue 88
Submitted-by: chip@chinacat.Unicom.COM (Chip Rosenthal)
Archive-name: enh-du/part01

I mentioned over in news.software.b that I have a script which generates
USENET disk usage and readership reports.  I posted the following example
of its output:

  +---------------------------------------------------------------------------
  | newsgroup              read  0days  1days  3days  5days  7days 15days
  | rec.arts.movies           1   1070    550      4      4      4      4
  | rec.arts.sf-lovers        1    846    544     30      0      0      0
  | news.groups               1    834    198      0      0      0      0
  | news.lists                1    732     58      0      0      0      0
  | news.announce.newusers    3    700      0      0      0      0      0
  +---------------------------------------------------------------------------

The guts behind this report is an enhanced version of "du".  The du
enhancement was originally started to add some badly desired features.
For example, I always wanted a way to tell du not to accumulate the usage
of ./alt/sources/d into ./alt.sources.

An unexpected result was that even after throwing in these features, this
implementation ran significantly faster the other du's I compared it
against.  Even more surprising, I found other du's reporting wrong results.
In particular, they botched the "indirect block" calculations on large
files.

This program uses the statfs() call, which I believe limits it to
System V Release 2 systems, compatible implementations, and descendants.
It has been tested on ISC UNIX 2.0.2 and SCO XENIX 386 2.3.

#! /bin/sh
# this is a "shar" archive - run through "/bin/sh" to extract 9 files:
#   README du.h patchlevel.h du.c duentry.c fsinfo.c Makefile du.man ngsizes
# Wrapped by bin@chinacat on Sun Sep  9 10:25:19 CDT 1990
# Unpacking this archive requires:  sed test wc (possibly mkdir)
# Existing files will not be clobbered unless "-c" is specified on the cmd line.
if test -f README -a "$1" != "-c" ; then
    echo "README: file exists - will not be overwritten"
else
    echo "x - README (file 1 of 9, 2906 chars)"
    sed -e 's/^X//' << 'END_OF_FILE_README' > README
X"du" is an enhanced replacement for the standard "du" disk usage summary
Xutility.  It's benefits are as follows:
X
X  - It's fast.  Compared to the SCO XENIX 2.3 and the Interactive UNIX
X    2.0.2, it runs somewhere between 50% to 65% of the time.
X
X  - It works.  The aforementioned du's report wrong results, and in
X    particular seem to botch the indirect block calculation.
X
X  - This version is very good about filesystems with different block
X    sizes.  It will do all internal calculations in terms of the native
X    filesystem blocksize.  Through the '-b' option you can either report
X    these results or normalize to a uniform "reporting blocksize".  For
X    example, "-b512" will give the same results as the standard du (except
X    when it botches the indirect calculation).  The default reporting
X    blocksize is set by REPORT_BLKSIZE, and may be zero to use the native
X    blocksize in the report.
X
X  - It has a number feature which you wish your "du" had, for example the
X    ability to *not* accumulate usage of a subdirectory into the parent
X    directory's usage.  This is real nice for du'ing your USENET news
X    spool directory.
X
X  - It allows the usage to be broken down by file age.  For example, the
X    command "du -c 0,7,30" will print four columns:  the usage, the usage
X    by entries a week or older, the usage by entries a month or older,
X    and the entry name.
X
X  - It deals well with links.  It doesn't have limitations that some other
X    du's do in terms of only being able to hold so many links or problems
X    with links to across directory trees.  It will handle all the links
X    you've got, and only report them once.
X
X
XInstallation:
X
X  - Customize the definitions at the front of "du.h" for your installation.
X
X  - Run a make.
X
X  - Install the "du" executable and "du.man" manual page in the appropriate
X    places.
X
X  - The included "ngsizes" script summarizes the disk usage in your USENET
X    spool directory, broken down by newsgroup and age.  If you'd like to
X    use this script,  there are some definitions at the top you will need
X    to customize.
X
X
XFine Print:
X
X    This program is copyright 1990, Unicom Systems Development.  All
X    rights reserved.  This software is provided "as is".  No warranties
X    have been expressed or implied.  You are granted license to compile,
X    execute, modify, copy, and transfer this program provided:  (1) all
X    copyright notices remain intact, (2) any modifications are clearly
X    marked as such, (3) you may not receive any direct profits from the
X    distribution of this program, and (4) your use of this software
X    indicates your agreement to hold Unicom Systems Development harmless
X    for any direct and/or incidental damages resulting from such use.
X
X
XComments, suggestions, and bug reports are welcomed to the address below.
X
XChip Rosenthal
X<chip@chinacat.Unicom.COM>
X
X@(#) README 1.2 90/09/08 14:38:51
END_OF_FILE_README
    size="`wc -c < README`"
    if test 2906 -ne "$size" ; then
	echo "README: extraction error - got $size chars"
    fi
fi
if test -f du.h -a "$1" != "-c" ; then
    echo "du.h: file exists - will not be overwritten"
else
    echo "x - du.h (file 2 of 9, 4816 chars)"
    sed -e 's/^X//' << 'END_OF_FILE_du.h' > du.h
X/* @(#) du.h 1.3 90/09/09 10:24:49 */
X
X/*
X * Package:	du - Enhanced "du" disk usage replacement.
X * File:	du.h - Header definitions.
X *
X * Sat Sep  8 14:34:56 1990 - Chip Rosenthal <chip@chinacat.Unicom.COM>
X *	Cleanup for distribution.
X * Tue Apr 17 21:50:58 1990 - Chip Rosenthal <chip@chinacat.Unicom.COM>
X *	Original composition.
X *
X * Copyright 1990, Unicom Systems Development.  All rights reserved.
X * See accompanying README file for terms of distribution and use.
X */
X
X
X#define VERSION		"1"
X
X/*
X * PRINT_ERRORS - If enabled, "du" will print diagnostic messages (e.g.
X * permission denied, etc.).  If disabled, "du" will print these messages
X * only if "-r" is given.  The latter behaviour is compatible with the
X * standard system "du".  The former behaviour is more sensible.
X */
X#define PRINT_ERRORS	/**/
X
X/*
X * REPORT_BLOCKSIZE - The size of a block (in bytes) used in reporting
X * disk usage.  Note that "du" will use the actual block size of a filesystem
X * when computing the usage of an entry on that filesystem, but will then
X * normalize all the results in terms of the reporting blocksize.  A
X * value of "512" will produce results compatible with the standard system
X * "du".  A value of "0" will use the native filesystem block size.  The
X * "-b" command line option may be used to override this default.
X */
X#define REPORT_BLKSIZE	512
X
X/*
X * MAX_BREAK - The maximum number of breakdown catagories which may be
X * specified with the "-c" option.  This value will effect the size of
X * (struct dskusage), and only a few of them are created, and thus this
X * can be a reasonably large number without too much impact.
X */
X#define MAX_BREAK	64
X
X/*
X * BROKE_MNTTAB - If defined, "du" will use it's own private description
X * of the /etc/mnttab file rather than including /usr/include/mnttab.h.
X * If you are running Interactive UNIX 2.0.2 you *must* enable this - your
X * <mnttab.h> is broken.
X */
X/*#define BROKE_MOUNTAB	/**/
X
X
X/*****************************************************************************
X *
X * End of site-specific customizations.
X *
X ****************************************************************************/
X
X#define TRUE		1
X#define FALSE		0
X
X/*
X * Error severity codes.
X */
X#define ERR_WARN	0
X#define ERR_ABORT	1
X
X#ifndef Reg
X# define Reg		register
X#endif
X
X#ifdef INTERN
X# define EXTERN
X#else
X# define EXTERN		extern
X#endif
X
X/*
X * A boolean true/false value.
X */
Xtypedef int		BOOL;
X
X/*
X * Datatype used to accumulate disk usage stats.
X */
Xstruct dskusage {
X    long b[MAX_BREAK];		/*  per days as specified Breakdown[]	      */
X};
X
X/*
X * Structure used to store information on mounted filesystems.
X */
Xstruct fsinfo {
X    int dev;			/* the device number			*/
X    int nino;			/* number of available inodes		*/
X    int bsize;			/* size of a block on this device	*/
X    int nindir;			/* number direct addresses in a block   */
X    unsigned char *idone;	/* bit vector of linked inodes done	*/
X    struct fsinfo *next;	/* pointer to next item in linked list	*/
X};
X
X/*
X * General globals.
X */
XEXTERN char *Progname;		/* this program's name			      */
XEXTERN char *Curr_dir;		/* working dir where prog was started	      */
XEXTERN long Curr_time;		/* time at which du was started		      */
X
X/*
X * Boolean options set by command line switches.
X */
XEXTERN BOOL Accum_subdirs;	/* add usage of a subdir into parent's usage  */
XEXTERN BOOL All_entries;	/* show everything, not just directories      */
XEXTERN BOOL Cross_filesys;	/* continue down dirs across filesystems      */
XEXTERN BOOL Descend_dirs;	/* follow directories recursively	      */
XEXTERN BOOL Suppress_repeats;	/* report multiply linked files only once     */
XEXTERN BOOL Print_errors;	/* display error messages		      */
XEXTERN BOOL Skip_links;		/* ignore multiply-linked regular files	      */
XEXTERN BOOL Total_only;		/* only print total usage for entire dir tree */
X
X/*
X * Other options set by command line switches.
X */
XEXTERN int Report_blksize;	/* report usage in terms of blocks this size  */
XEXTERN int Num_break;		/* breakdown usage reported to this many cols */
XEXTERN int Breakdown[MAX_BREAK];/* each col is usage more than this many days */
X
X/*
X * Local procedures.
X */
Xvoid		errmssg();	/* display a diagnostic message		     */
Xvoid		du_entry();	/* determine usage of a particular entry     */
Xvoid		fs_initinfo();	/* initialize filesystem information	     */
Xstruct fsinfo *	fs_getinfo();	/* get filesystem information		     */
Xint		fs_linkdone();	/* determine whether a file has been visited */
Xlong		fs_numblocks();	/* count number of blocks used by an entry   */
Xvoid		set_usage();	/* set a disk usage value		     */
Xvoid		add_usage();	/* accumulate a disk usage value	     */
Xvoid		print_usage();	/* display a disk usage value		     */
Xvoid *		xmalloc();	/* allocate memory with error checking	     */
X
END_OF_FILE_du.h
    size="`wc -c < du.h`"
    if test 4816 -ne "$size" ; then
	echo "du.h: extraction error - got $size chars"
    fi
fi
if test -f patchlevel.h -a "$1" != "-c" ; then
    echo "patchlevel.h: file exists - will not be overwritten"
else
    echo "x - patchlevel.h (file 3 of 9, 516 chars)"
    sed -e 's/^X//' << 'END_OF_FILE_patchlevel.h' > patchlevel.h
X
X/* @(#) patchlevel.h 1.1 90/09/08 14:45:00 */
X
X/*
X * Package:	du - Enhanced "du" disk usage replacement.
X * File:	patchlevel.h - Version comments.
X *
X * Sat Sep  8 14:34:56 1990 - Chip Rosenthal <chip@chinacat.Unicom.COM>
X *	Cleanup for distribution.
X * Tue Apr 17 21:50:58 1990 - Chip Rosenthal <chip@chinacat.Unicom.COM>
X *	Original composition.
X *
X * Copyright 1990, Unicom Systems Development.  All rights reserved.
X * See accompanying README file for terms of distribution and use.
X */
X
X
X#define PATCHLEVEL	0
X
END_OF_FILE_patchlevel.h
    size="`wc -c < patchlevel.h`"
    if test 516 -ne "$size" ; then
	echo "patchlevel.h: extraction error - got $size chars"
    fi
fi
if test -f du.c -a "$1" != "-c" ; then
    echo "du.c: file exists - will not be overwritten"
else
    echo "x - du.c (file 4 of 9, 8182 chars)"
    sed -e 's/^X//' << 'END_OF_FILE_du.c' > du.c
X/* @(#) du.c 1.2 90/09/08 14:38:52 */
X
X/*
X * Package:	du - Enhanced "du" disk usage report generator.
X * File:	du.c - Main program.
X *
X * Sat Sep  8 14:34:56 1990 - Chip Rosenthal <chip@chinacat.Unicom.COM>
X *	Cleanup for distribution.
X * Tue Apr 17 21:50:58 1990 - Chip Rosenthal <chip@chinacat.Unicom.COM>
X *	Original composition.
X *
X * Copyright 1990, Unicom Systems Development.  All rights reserved.
X * See accompanying README file for terms of distribution and use.
X */
X
X#include <stdio.h>
X#define INTERN
X#include "du.h"
X#include "patchlevel.h"
X
Xstatic char Copyright[] =
X    "@(#) Copyright 1990, Unicom Systems Development.  All rights reserved.";
Xstatic char SccsID[] = "@(#) du.c 1.2 90/09/08 14:38:52";
X
X#define USAGE	"usage: %s [ options ] [ path ... ]    (try '-h' for help)\n"
X
X/*
X * Local procedures.
X */
Xstatic void do_help();
Xstatic void set_breakdown();
X
X/*
X * External procedures.
X */
Xextern char *getcwd();
Xextern char *strtok();
Xextern long time();
Xextern void *malloc();
Xextern void exit();
X
X
Xmain(argc,argv)
Xint argc;
Xchar *argv[];
X{
X    int i;
X    extern char *optarg;
X    extern int optind;
X
X    /*
X     * Initialize.
X     */
X    Progname		=argv[0];
X    Accum_subdirs	=TRUE;	/* add usage of a subdir into parent's usage  */
X    All_entries		=FALSE;	/* just show directories		      */
X    Cross_filesys	=TRUE;	/* continue down dirs across filesystems      */
X    Descend_dirs	=TRUE;	/* follow directories recursively	      */
X    Suppress_repeats	=TRUE;	/* report multiply linked files only once     */
X#ifdef PRINT_ERRORS
X    Print_errors	=TRUE;	/* enable error messages		      */
X#else
X    Print_errors	=FALSE;	/* suppress error messages		      */
X#endif
X    Skip_links		=FALSE;	/* allow checking of multiply-linked files    */
X    Total_only		=FALSE;	/* print usage at each directory encountered  */
X    Report_blksize	=REPORT_BLKSIZE; /* block size used in reports	      */
X    Num_break		=1;	/* only present on column in the usage report */
X    Breakdown[0]	=0;	/* that col should include all existing files */
X
X    /* 
X     * Crack command line options.
X     */
X    while ( (i=getopt(argc, argv, "ab:c:dfhilrsu")) != EOF ) {
X	switch ( i ) {
X	    case 'a':	All_entries = TRUE;			break;
X	    case 'b':   Report_blksize = atoi(optarg);		break;
X	    case 'c':	set_breakdown(optarg);			break;
X	    case 'd':	Descend_dirs = FALSE;			break;
X	    case 'f':	Cross_filesys = FALSE;			break;
X	    case 'h':	do_help();				exit(0);
X	    case 'i':	Accum_subdirs = FALSE;			break;
X	    case 'l':	Suppress_repeats = FALSE;		break;
X	    case 'r':	Print_errors = TRUE;			break;
X	    case 's':	Total_only = TRUE;			break;
X	    case 'u':	Skip_links = TRUE;			break;
X	    default:	fprintf(stderr, USAGE, Progname);	exit(1);
X	}
X    }
X
X    /*
X     * Initialize the filesystem information tables.
X     */
X    fs_initinfo();
X
X    /*
X     * Get the starting directory in case we need to chdir.
X     */
X    if ( (Curr_dir=getcwd((char *)NULL, 256)) == NULL )
X	errmssg(ERR_ABORT,"couldn't get current working directory");
X
X    /*
X     * Get the time so we can do the breakdown of usage by age.
X     */
X    (void) time(&Curr_time);
X
X    /*
X     * If no arguments specified on the command line then do the current dir.
X     */
X    if ( optind == argc ) {
X	du_entry(".");
X	exit(0);
X    }
X
X    /*
X     * Do all the items given on the command line.
X     */
X    for ( i = optind ; i < argc ; ++i )
X	du_entry(argv[i]);
X    exit(0);
X
X    /*NOTREACHED*/
X}
X
X
Xstatic char *usage_text[] = {
X    "",
X    "du - version %V (patchlevel %L)",
X    "",
X    "  Usage:  %P [ options ] [ path ... ]",
X    "",
X    "  Options:",
X    "    -a          Report all files encountered, not just directories.",
X    "    -b n        Report as if disk blocks were 'n' bytes (default %B).",
X    "                  (A '0' value reports in native filesystem block size.)",
X    "    -c n,n,...  Breakdown by age, one col for each 'n' days or older.",
X    "    -d          Do not descend into directories encountered.",
X    "    -f          Do cross filesystems.",
X    "    -h          Display this help message.",
X    "    -i          Do not accumulate subdirectories' usage into parent dir.",
X    "    -l          Count multiply linked files each time encountered.",
X#ifndef PRINT_ERRORS
X    "    -r          Print (don't suppress) errors which occur during scan.",
X#endif
X    "    -s          Only print a total for each argument on the command line.",
X    "    -u          Skip (do not count) multiply linked files entirely.",
X    "",
X    "Copyright 1990, Unicom Systems Development.  All rights reserved.",
X    "See distributed README file for terms of distribution and use.",
X    "",
X    NULL
X};
X
X
Xstatic void do_help()
X{
X    Reg char *s, **linep;
X
X    for ( linep = usage_text ; *linep != NULL ; ++linep ) {
X	for ( s = *linep ; *s != '\0' ; ++s ) {
X	    if ( *s == '%' ) {
X		switch ( *++s ) {
X		    case 'B': fprintf(stderr,"%d",REPORT_BLKSIZE);	break;
X		    case 'P': fputs(Progname,stderr);			break;
X		    case 'V': fputs(VERSION,stderr);			break;
X		    case 'L': fprintf(stderr,"%d",PATCHLEVEL);		break;
X		    default:  putc('%',stderr); putc(*s,stderr);	break;
X		}
X	    } else {
X		putc(*s,stderr);
X	    }
X	}
X	putc('\n', stderr);
X    }
X    exit(0);
X}
X
X
Xstatic void set_breakdown(str)
Xchar *str;
X{
X    char *s;
X
X    Num_break = 0;
X    while ( (s=strtok(str, " \t,")) != NULL ) {
X	if ( Num_break >= MAX_BREAK ) {
X	    fprintf(stderr, "%s: too many breakdown catagories\n", Progname);
X	    exit(1);
X	}
X	if ( (Breakdown[Num_break++]=atoi(s)) == 0 && *s != '0' ) {
X	    fprintf(stderr, "%s: bad breakdown value '%s'\n", Progname, s);
X	    exit(1);
X	}
X	str = NULL;
X    }
X
X    if ( Num_break == 0 ) {
X	fprintf(stderr, "%s: no breakdown catagories specified\n", Progname);
X	exit(1);
X    }
X
X}
X
X
Xvoid *xmalloc(n)
Xunsigned n;
X{
X    char *s;
X    if ( (s=malloc(n)) == NULL ) {
X	fputs("malloc: out of space\n",stderr);
X	exit(1);
X    }
X    return s;
X}
X
X
X/*
X * Error Message Interface - The errmssg() routine uses a printf-like syntax
X * to display an error message.  If "errno" is nonzero, it will be included
X * in the diagnostic message.
X */
X
X#include <varargs.h>
X
X/*VARARGS1*/
Xvoid errmssg(severity, fmt, va_alist)
Xint severity;
Xchar *fmt;
Xva_dcl
X{
X    va_list args;
X    int save_errno;
X    extern int errno;
X    extern char *sys_errlist[];
X
X    if ( !Print_errors && severity < ERR_ABORT )
X	return;
X
X    save_errno = errno;
X    va_start(args);
X    fprintf(stderr, "%s: ", Progname);
X    vfprintf(stderr, fmt, args);
X    if ( save_errno > 0 )
X	fprintf(stderr, " (%s)", sys_errlist[save_errno]);
X    putc('\n',stderr);
X    va_end(args);
X
X    if ( severity >= ERR_ABORT )
X	exit(1);
X}
X
X
X/*
X * Disk Usage Functions - Disk usage is stored in a (struct dskusage) datatype.
X * The following routines provide all the required manipulations of this
X * datatype, and no knowledge of the internals of this structure exists
X * outside these routines.  The following functions are provided:
X *
X *   set_usage() - Load a (struct dskusage) from file information.
X *   add_usage() - Accumulate data from one (struct dskusage) into another.
X *   print_usage() - Print statistics from a (struct dskusage).
X */
X
X#include <sys/types.h>
X#include <sys/stat.h>
X
Xvoid set_usage(usage, sbufp, nblocks)
XReg struct dskusage *usage;
Xstruct stat *sbufp;
XReg long nblocks;
X{
X    Reg int i;
X    long ndays;
X
X     /*
X      * The following luckily works out OK.  If a file is created after "du"
X      * is started, the "Curr_time-sbufp->st_mtime" value will be a small
X      * negative number, however when divided by "60*60*24" it will truncate
X      * to zero, so it will be counted in the breakdown OK.
X      */
X    ndays = (Curr_time - sbufp->st_mtime) / ( 60*60*24 /* sec per day */ );
X
X    for ( i = Num_break-1 ; i >= 0 ; --i )
X	usage->b[i] = ( Breakdown[i] <= ndays ? nblocks : 0L );
X}
X
Xvoid add_usage(tot_usage, ent_usage)
XReg struct dskusage *tot_usage, *ent_usage;
X{
X    Reg int i;
X    for ( i = 0 ; i < Num_break ; ++i )
X	tot_usage->b[i] += ent_usage->b[i];
X}
X
Xvoid print_usage(name, usage)
Xchar *name;
Xstruct dskusage *usage;
X{
X    int i;
X    for ( i = 0 ; i < Num_break ; ++i )
X	printf("%ld\t", usage->b[i]);
X    printf("%s\n", name);
X}
X
END_OF_FILE_du.c
    size="`wc -c < du.c`"
    if test 8182 -ne "$size" ; then
	echo "du.c: extraction error - got $size chars"
    fi
fi
if test -f duentry.c -a "$1" != "-c" ; then
    echo "duentry.c: file exists - will not be overwritten"
else
    echo "x - duentry.c (file 5 of 9, 6920 chars)"
    sed -e 's/^X//' << 'END_OF_FILE_duentry.c' > duentry.c
X
X/* @(#) duentry.c 1.2 90/09/08 14:38:56 */
X
X/*
X * Package:	du - Enhanced "du" disk usage report generator.
X * File:	duentry - Scan entries for disk usage.
X *
X * The "du_entry()" routine provides the disk usage of a filesystem entry.
X * To process directories, the "du_dir()" routine will be run recursively.
X *
X * Sat Sep  8 14:34:56 1990 - Chip Rosenthal <chip@chinacat.Unicom.COM>
X *	Cleanup for distribution.
X * Tue Apr 17 21:50:58 1990 - Chip Rosenthal <chip@chinacat.Unicom.COM>
X *	Original composition.
X *
X * Copyright 1990, Unicom Systems Development.  All rights reserved.
X * See accompanying README file for terms of distribution and use.
X */
X
X#include <stdio.h>
X#include <string.h>
X#include <sys/types.h>
X#include <sys/stat.h>
X#include "du.h"
X
X#ifdef M_XENIX
X#   include <sys/ndir.h>
X    typedef struct direct DIRENT;
X#else
X#   include <dirent.h>
X    typedef struct dirent DIRENT;
X#endif
X
Xstatic char SccsID[] = "@(#) duentry.c 1.2 90/09/08 14:38:56";
X
X/*
X * Local procedures.
X */
XBOOL du_dir();
X
X/*
X * External procedures.
X */
Xextern DIR *opendir();
Xextern DIRENT *readdir();
Xextern void exit();
X
X
X/*
X * du_entry() - Initiate a disk usage report for a specified filesys entry.
X */
Xvoid du_entry(entry)
Xchar *entry;			/* name of the entry to check		      */
X{
X    struct stat		sbuf;		/* for stat info on this entry	      */
X    struct fsinfo	*fsinfop;	/* for filesys info on this entry     */
X    struct dskusage	tot_blocks;	/* to accumulate usage of this entry  */
X
X    /*
X     * Get the information on this entry.
X     */
X    if ( stat(entry, &sbuf) != 0 ) {
X	errmssg(ERR_WARN,"couldn't stat '%s'", entry);
X	return;
X    }
X    if ( (fsinfop = fs_getinfo((struct fsinfo *)NULL, &sbuf)) == NULL ) {
X	errmssg(ERR_WARN,"couldn't get filesystem info for '%s'", entry);
X	return;
X    }
X
X    switch ( sbuf.st_mode & S_IFMT ) {
X
X    case S_IFREG:
X	set_usage(&tot_blocks, &sbuf, fs_numblocks(fsinfop, &sbuf));
X	if ( All_entries || Total_only )
X	    print_usage(entry, &tot_blocks);
X	break;
X
X    case S_IFDIR:
X	if ( chdir(entry) != 0 ) {
X	    errmssg(ERR_WARN,"couldn't chdir to '%s'", entry);
X	    break;
X	}
X	if ( du_dir(entry, &sbuf, fsinfop, &tot_blocks) && Total_only )
X	    print_usage(entry, &tot_blocks);
X	if ( chdir(Curr_dir) != 0 )
X	    errmssg(ERR_ABORT,"couldn't chdir back to '%s'", Curr_dir);
X	break;
X
X    default:
X	if ( Print_errors ) {
X	    fprintf(stderr, "%s: '%s' is not a file or directory\n",
X		Progname, entry);
X	}
X	return;
X
X    }
X
X}
X
X
X/*
X * du_dir() - Scan through a specific directory and report its disk space
X * usage.  No information is returned.  Depending upon certain options,
X * this procedure might recursively scan encountered directories, or
X * accumulate subdirectory usage in this directory.
X */
XBOOL du_dir(dir_name, dir_statp, dir_fsinfop, dir_blocks_p)
Xchar		*dir_name;	/* pathname to the directory to scan	      */
Xstruct stat	*dir_statp;	/* stat information on this directory	      */
Xstruct fsinfo	*dir_fsinfop;	/* info on filesystem containing dir	      */
XReg struct dskusage *dir_blocks_p; /* storage for the usage		      */
X{
X    Reg DIRENT		*dp;		/* current entry being checked	      */
X    Reg char		*ent_basename;	/* ptr to basename portion of pathname*/
X    struct dskusage	ent_blocks;	/* usage statistics for current entry */
X    struct stat		ent_stat;	/* inode info for current entry	      */
X    struct fsinfo	*ent_fsinfop;	/* info on filesys with current entry */
X    DIR			*dirp;		/* stream for dir being searched      */
X    char		ent_pathname[MAXNAMLEN]; /* full pathname of entry    */
X    long		nblocks;
X
X    /*
X     * Setup a buffer to hold the full pathname of the entry being examined.
X     * We can just place a filename at "ent_basename" to make the full pathname.
X     */
X    ent_basename = strcpy(ent_pathname, dir_name) + strlen(dir_name);
X    *ent_basename++ = '/';
X
X    /*
X     * Initialize the block count with the usage by the directory itself.
X     */
X    set_usage(dir_blocks_p, dir_statp, fs_numblocks(dir_fsinfop, dir_statp));
X
X    /*
X     * Open up the directory so we can scan it.
X     */
X    if ( (dirp=opendir(".")) == NULL ) {
X	errmssg(ERR_WARN,"couldn't open dir '%s'", dir_name);
X	return FALSE;
X    }
X
X    /*
X     * Go through each entry in the directory.
X     */
X    while ( (dp=readdir(dirp)) != NULL ) {
X
X	/*
X	 * Skip the "." and ".." entries.
X	 */
X	if (
X	    dp->d_name[0] == '.' && (
X		dp->d_name[1] == '\0' ||
X		( dp->d_name[1] == '.' && dp->d_name[2] == '\0' )
X	    )
X	) {
X	    continue;
X	}
X
X	/*
X	 * Create the full pathname to this entry.
X	 */
X	(void) strcpy(ent_basename, dp->d_name);
X
X	/*
X	 * Get the information on this entry.
X	 */
X	if ( stat(ent_basename, &ent_stat) != 0 ) {
X	    errmssg(ERR_WARN,"couldn't stat '%s'", ent_pathname);
X	    continue;
X	}
X
X	/*
X	 * How we process this entry depends upon what type it is.
X	 */
X	switch ( ent_stat.st_mode & S_IFMT ) {
X
X	/*
X	 * For files, accumulate the disk usage into the directory total.
X	 */
X	case S_IFREG:
X
X	    /*
X	     * See if we want to process this file.
X	     */
X	    if ( ent_stat.st_nlink > 1 ) {
X		if ( Skip_links )
X		    break;
X		if ( Suppress_repeats && fs_linkdone(dir_fsinfop, &ent_stat) )
X		    break;
X	    }
X
X	    nblocks = fs_numblocks(dir_fsinfop, &ent_stat);
X	    set_usage(&ent_blocks, &ent_stat, nblocks);
X	    add_usage(dir_blocks_p, &ent_blocks);
X	    if ( All_entries )
X		print_usage(ent_pathname, &ent_blocks);
X	    break;
X
X	/*
X	 * For directories, we might need to scan recursively.
X	 */
X	case S_IFDIR:
X
X	    /*
X	     * Check if we are crossing a mount point.
X	     */
X	    if ( !Cross_filesys && dir_statp->st_dev != ent_stat.st_dev )
X		break;
X
X	    /*
X	     * Get the filesystem information on this diretory.
X	     */
X	    ent_fsinfop = fs_getinfo(dir_fsinfop, &ent_stat);
X	    if ( ent_fsinfop == NULL ) {
X		errmssg(ERR_WARN,"couldn't get filesystem info for '%s'",
X		    ent_pathname);
X		break;
X	    }
X
X	    /*
X	     * If we shouldn't descend into this dir, then just get its size.
X	     */
X	    if ( !Descend_dirs ) {
X		nblocks = fs_numblocks(ent_fsinfop, &ent_stat);
X		set_usage(&ent_blocks, &ent_stat, nblocks);
X		add_usage(dir_blocks_p, &ent_blocks);
X		if ( !Total_only )
X		    print_usage(ent_pathname, &ent_blocks);
X		break;
X	    }
X
X	    /*
X	     * Go get the usage on this directory.
X	     */
X	    if ( chdir(ent_basename) != 0 ) {
X		errmssg(ERR_WARN,"couldn't chdir to '%s'", ent_pathname);
X		break;
X	    }
X	    if ( du_dir(ent_pathname, &ent_stat, ent_fsinfop, &ent_blocks) ) {
X		if ( Accum_subdirs )
X		    add_usage(dir_blocks_p, &ent_blocks);
X	    }
X	    if ( chdir("..") != 0 ) {
X		Print_errors = TRUE;
X		errmssg(ERR_WARN,"couldn't chdir back to '%s'", dir_name);
X		exit(1);
X	    }
X	    break;
X
X	/*
X	 * Special files are ignored.
X	 */
X	default:
X	    break;
X
X	}
X
X    }
X
X    /*
X     * The current directory is complete.
X     */
X    (void) closedir(dirp);
X
X    if ( !Total_only )
X	print_usage(dir_name, dir_blocks_p);
X    return TRUE;
X
X}
X
END_OF_FILE_duentry.c
    size="`wc -c < duentry.c`"
    if test 6920 -ne "$size" ; then
	echo "duentry.c: extraction error - got $size chars"
    fi
fi
if test -f fsinfo.c -a "$1" != "-c" ; then
    echo "fsinfo.c: file exists - will not be overwritten"
else
    echo "x - fsinfo.c (file 6 of 9, 7530 chars)"
    sed -e 's/^X//' << 'END_OF_FILE_fsinfo.c' > fsinfo.c
X/* @(#) fsinfo.c 1.2 90/09/08 14:38:57 */
X
X/*
X * Package:	du - Enhanced "du" disk usage report generator.
X * File:	fsinfo.c - File-system information routines.
X *
X * The following routines are to provide the filesystem-specific support:
X *
X *   fs_initinfo() - Initializes internal filesystem tables.
X *   fs_getinfo() - Get filesystem information for an entry.
X *   fs_linkdone() - Determines whether a file has been visited already.
X *   fs_numblocks() - Calculates disk usage of an entry.
X *
X * A linked list of (struct fsinfo) is maintained, one element per mounted
X * filesystem.  The "fs_initinfo()" routine initializes this list.  The
X * "fs_getinfo()" routine locates the element associated with the filesystem
X * containing a particular file.  To minimize overhead, the "fs_getinfo()"
X * routine should be called only when we change directories, and the
X * information on the current directory should be used for all the items
X * in that directory.  The "fs_linkdone()" and "fs_numblocks()" routines
X * require the pointer returned by "fs_getinfo()".
X *
X * Sat Sep  8 14:34:56 1990 - Chip Rosenthal <chip@chinacat.Unicom.COM>
X *	Cleanup for distribution.
X * Tue Apr 17 21:50:58 1990 - Chip Rosenthal <chip@chinacat.Unicom.COM>
X *	Original composition.
X *
X * Copyright 1990, Unicom Systems Development.  All rights reserved.
X * See accompanying README file for terms of distribution and use.
X */
X
X#include <stdio.h>
X#include <sys/types.h>
X#include <sys/stat.h>
X#include <sys/statfs.h>
X#include <mnttab.h>
X#include "du.h"
X
Xstatic char SccsID[] = "@(#) fsinfo.c 1.2 90/09/08 14:38:57";
X
X/*
X * Mount table stuff.
X */
X#ifndef PNMNTTAB
X#   define PNMNTTAB "/etc/mnttab"
X#endif
X#ifndef ISMNTFREE
X#   define ISMNTFREE(m) ( (m)->mt_dev[0] == '\0' )
X#endif
X#ifdef BROKE_MNTTAB			/* ISC 2.0.2 botched <mnttab.h> */
X#   define mnttab mnttab_kludge
X    struct mnttab_kludge {
X	char mt_dev[32];
X	char mt_filsys[32];
X	char mt_stuff[12];
X    };
X#endif
X
X/*
X * The head of a linked list list of filesystem information records.
X */
Xstruct fsinfo *Fsinfo_list;
X
X/*
X * External procedures.
X */
Xextern void *memset();
X
X
X/*
X * fs_initinfo() - Initializes list of filesystem information.
X */
Xvoid fs_initinfo()
X{
X    struct fsinfo	*fsp, *fsp_tail;
X    struct mnttab	mbuf;
X    struct stat		sbuf;
X    struct statfs	fsbuf;
X    FILE		*fp;
X    int			n;
X
X    Fsinfo_list = fsp_tail = NULL;
X
X    /*
X     * Open up the mount table.
X     */
X    if ( (fp=fopen(PNMNTTAB,"r")) == NULL )
X	errmssg(ERR_ABORT,"couldn't open '%s'", PNMNTTAB);
X
X    /*
X     * Go through mount table and look for all mounted filesystems.
X     */
X    while ( fread( (char *)&mbuf, sizeof(struct mnttab), 1, fp ) == 1 ) {
X
X	/*
X	 * Ignore empty slots.
X	 */
X	if ( ISMNTFREE(&mbuf) )
X	    continue;
X
X	/*
X	 * Get the information on this filesystem.
X	 */
X	if ( stat(mbuf.mt_filsys,&sbuf) != 0 ) {
X	    errmssg(ERR_WARN,"couldn't stat '%s'", mbuf.mt_filsys);
X	    continue;
X	}
X	if ( statfs(mbuf.mt_filsys,&fsbuf,sizeof(struct statfs),0) != 0 ) {
X	    errmssg(ERR_WARN,"couldn't statfs '%s'", mbuf.mt_filsys);
X	    continue;
X	}
X
X	/*
X	 * Allocate the filesystem information structure.
X	 */
X	fsp = (struct fsinfo *) xmalloc( sizeof(struct fsinfo) );
X	fsp->dev = sbuf.st_dev;
X	fsp->nino = fsbuf.f_files;
X	fsp->bsize = fsbuf.f_bsize;
X	fsp->nindir = fsp->bsize / sizeof(daddr_t);
X	fsp->next = NULL;
X
X	/*
X	 * Create the bit vector which indicates multiply-linked inodes done.
X	 *   See fs_linkdone() for information on this bitvector.
X	 */
X	n = fsp->nino/8 + 1;
X	fsp->idone = (unsigned char *) xmalloc((unsigned)n);
X	memset(fsp->idone,0,n);
X
X	/*
X	 * Attach the filesystem information to the end of the list.
X	 */
X	if ( Fsinfo_list == NULL )
X	    Fsinfo_list = fsp;
X	else
X	    fsp_tail->next = fsp;
X	fsp_tail = fsp;
X
X    }
X
X    (void) fclose(fp);
X
X}
X
X
X/*
X * fs_getinfo() - Get filesystem information on a entry.
X */
Xstruct fsinfo *fs_getinfo(fsp,sbufp)
XReg struct fsinfo	*fsp;	/* filesystem info on dir containing entry    */
XReg struct stat		*sbufp;	/* stat information on the entry	      */
X{
X    /*
X     * If we already have info on the directory containing this entry and
X     * this entry doesn't cross a mount point, then we can use the same info.
X     */
X    if ( fsp != NULL && fsp->dev == sbufp->st_dev )
X	return fsp;
X
X    /*
X     * Search the linked list for this filesystem.
X     */
X    fsp = Fsinfo_list;
X    while ( fsp != NULL && fsp->dev != sbufp->st_dev )
X	fsp = fsp->next;
X    return fsp;
X}
X
X
X/*
X * fs_linkdone() - Determines whether a file has been visited already.
X *
X *   This procedure implements the logic to avoid recounting of multiply
X *   linked files.  Each fsinfo structure contains a bit vector for all of
X *   the filesystem's inodes.  This procedure uses this vector to see if
X *   a file has been done already.  The first time this procedure is called
X *   for a particular inode number, we return FALSE and mark it in the bit
X *   vector.  All following times this procedure is called we return TRUE.
X */
Xint fs_linkdone(fsp,sbufp)
XReg struct fsinfo *fsp;
XReg struct stat *sbufp;
X{
X    Reg unsigned char *rowp;
X    int mask;
X
X    /*
X     * Locate the bit within the vector for this inode.
X     */
X    rowp = fsp->idone + ( sbufp->st_ino >> 3 );
X    mask = 1 << (sbufp->st_ino & 07);
X
X    /*
X     * If the bit is set then this link was already done.
X     */
X    if ( *rowp & mask )
X	return TRUE;
X
X    /*
X     * Set the bit and indicate the link hasn't been done yet.
X     */
X    *rowp |= mask;
X    return FALSE;
X}
X
X
X#define DIRBLKS		10			/* num direct addrs in inode  */
X#define CEIL_DIV(A,B)  ( ((A)+(B)-1) / (B) )	/* calculate "ceiling(A/B)"   */
X
X/*
X * fs_numblocks() - Calculates disk usage of an entry.
X */
Xlong fs_numblocks(fsp,sbufp)
XReg struct fsinfo *fsp;
Xstruct stat *sbufp;
X{
X    Reg long	n_used;		/* num blocks used, incl overhead	*/
X    Reg long	n_to_place;	/* num data blocks to be placed		*/
X    long	n_single_ind;	/* scratch single indirect block cntr	*/
X    long	n_double_ind;	/* scratch double indirect block cntr	*/
X
X    /*
X     * Determine the number of blocks which are required to store this file.
X     */
X    n_used = CEIL_DIV( sbufp->st_size , fsp->bsize );
X    n_to_place = n_used;
X
X    /*
X     * The first DIRBLKS blocks are directly addressed through the inode.
X     */
X    n_to_place -= DIRBLKS;
X    if ( n_to_place <= 0 )
X	goto done;
X
X    /*
X     * With the single indirect block, we can get another "nindir" blocks.
X     */
X    ++n_used;
X    n_to_place -= fsp->nindir;
X    if ( n_to_place <= 0 )
X	goto done;
X
X    /*
X     * With the double indirect block, we can get another "nindir" single
X     * indirect blocks, for a total of another "nindir**2" data blocks.
X     */
X    n_single_ind = CEIL_DIV( n_to_place , fsp->nindir );
X    if ( n_single_ind > fsp->nindir )
X	n_single_ind = fsp->nindir;
X    n_used += 1 + n_single_ind;
X    n_to_place -= n_single_ind * fsp->nindir ;
X    if ( n_to_place <= 0 )
X	goto done;
X
X    /*
X     * With the triple indirect block, we can get another "nindir" double
X     * indirect blocks, for another "nindir**2" single indirect blocks, for
X     * a total of another "nindir**3" data blocks.
X     */
X    n_single_ind = CEIL_DIV( n_to_place , fsp->nindir );
X    n_double_ind = CEIL_DIV( n_single_ind , fsp->nindir );
X    n_used += 1 + n_double_ind + n_single_ind;
X
Xdone:
X
X    /*
X     * Convert the usage from native blocksize to reporting blocksize.
X     */
X    if ( Report_blksize == 0 || Report_blksize == fsp->bsize )
X	return n_used;
X    else
X	return CEIL_DIV( n_used*fsp->bsize , Report_blksize );
X}
X
END_OF_FILE_fsinfo.c
    size="`wc -c < fsinfo.c`"
    if test 7530 -ne "$size" ; then
	echo "fsinfo.c: extraction error - got $size chars"
    fi
fi
if test -f Makefile -a "$1" != "-c" ; then
    echo "Makefile: file exists - will not be overwritten"
else
    echo "x - Makefile (file 7 of 9, 1394 chars)"
    sed -e 's/^X//' << 'END_OF_FILE_Makefile' > Makefile
X
X# @(#) Makefile 1.2 90/09/08 14:38:50
X# Makefile for "du" (generated by makemake version 1.00.09)
X# Created by bin@chinacat on Sat Sep  8 13:59:09 CDT 1990
X
XSHELL = /bin/sh
XCC = cc
XDEFS = 
XCOPTS = -O
XLOPTS = 
XLIBS = -lx
XDEBUG = -g -DReg=
XLINTFLAGS = -DLINT
X
XTARG = du
XOTHERS = 
X
XSRCS = du.c duentry.c fsinfo.c
X
XOBJS = du.o duentry.o fsinfo.o
X
X# Any edits below this line will be lost if "makemake" is rerun!
X# Commands may be inserted after the '#%custom' line at the end of this file.
X
XCFLAGS = $(COPTS) $(DEFS) # $(DEBUG)
XLFLAGS = $(LOPTS) # $(DEBUG)
X
Xall:		$(TARG) $(OTHERS)
Xinstall:	all		; inst Install
Xclean:				; rm -f $(TARG) $(OBJS) a.out core $(TARG).lint
Xclobber:	clean		; inst -u Install
Xlint:		$(TARG).lint
X
X$(TARG):		$(OBJS)
X		$(CC) $(LFLAGS) -o $@ $(OBJS) $(LIBS)
X
X$(TARG).lint:	$(TARG)
X		lint $(LINTFLAGS) $(DEFS) $(SRCS) $(LIBS) > $@
X
Xdu.o: du.c du.h patchlevel.h
Xduentry.o: du.h duentry.c
Xfsinfo.o: du.h fsinfo.c
X
Xmake:		;
X		makemake -i -v1.00.09 -aMakefile \
X		    -DSHELL='$(SHELL)' -DCC='$(CC)' -DDEFS='$(DEFS)' \
X		    -DCOPTS='$(COPTS)' -DLOPTS='$(LOPTS)' -DLIBS='$(LIBS)' \
X		    -DDEBUG='$(DEBUG)' -DLINTFLAGS='$(LINTFLAGS)' \
X		    -DOTHERS='$(OTHERS)' $(TARG) $(SRCS)
X
X#%custom - commands below this line will be maintained if 'makemake' is rerun
X
XLIST = README du.h patchlevel.h $(SRCS) Makefile du.man ngsizes
X
Xshar:		du.shar
Xdu.shar:	$(LIST)		; shar $(LIST) > $@
X
END_OF_FILE_Makefile
    size="`wc -c < Makefile`"
    if test 1394 -ne "$size" ; then
	echo "Makefile: extraction error - got $size chars"
    fi
fi
if test -f du.man -a "$1" != "-c" ; then
    echo "du.man: file exists - will not be overwritten"
else
    echo "x - du.man (file 8 of 9, 4670 chars)"
    sed -e 's/^X//' << 'END_OF_FILE_du.man' > du.man
X.\" @(#) du.man 1.2 90/09/08 14:38:55
X.\"
X.\" Sat Sep  8 14:34:56 1990 - Chip Rosenthal <chip@chinacat.Unicom.COM>
X.\"	Cleanup for distribution.
X.\" Tue Apr 17 21:50:58 1990 - Chip Rosenthal <chip@chinacat.Unicom.COM>
X.\"	Original composition.
X.\"
X.TH DU 1
X.SH NAME
Xdu - Enhanced disk usage summary.
X.SH SYNTAX
X.B du
X[ options ] [ name ... ]
X.SH DESCRIPTION
X.I Du
Xdetermines the number of disk blocks used by
X.IR name .
X.P
XIf
X.I name
Xis a directory, then
X.I du
Xrecursively descends down the directory accumulating and reporting the
Xdisk usage at each subdirectory.  The usage at a directory will include
Xthe usage of all its subdirectories.
X.P
XSince the usage is reported only at directories, no result will be printed
Xif
X.I name
Xis a regular file.  (This may be changed with the
X.B \-a
Xand
X.B \-s
Xoptions described below.) If no
X.I name
Xis specified, then
X.I du
Xprovides the usage at the current working directory.
X.P
X.I Du
Xkeeps track of files which contain multiple links, and counts such files
Xonly once.  Large files require filesystem overhead called ``indirect
Xblocks''.  These blocks will be included as well as the actual data (direct)
Xblocks in the usage statistics.
X.P
XFilesystem storage is allocated in groups called ``blocks'', and the block
Xsize may be a filesystem dependent value.
X.I Du
Xcalculates the size of an entry in terms of the native filesystem block
Xsize, but may scale the reported results to a ``reporting'' blocksize.
XThis ensures that entry sizes are calculated correctly, and reported
Xconsistently.  The help option
X.RB ( \-h )
Xwill indicate the default reporting blocksize.  The
X.B \-b
Xoption can change this value.
X.P
XThe following options modify the behavior of
X.IR du :
X.IP  "\-a" 10
XThe usage of all items (i.e. regular files as well as directories) is
Xreported.
X.IP "\-b \fIsize\fR" 10
XThe usage is reported in terms of disk blocks \fIsize\fR bytes instead
Xof the actual disk block size of the filesystem.  If zero is specified,
Xconversion to a reporting blocksize is suppressed, and the values reported
Xare the actual number of blocks used.
X.IP "\-c \fIn\fR,..." 10
XThis option requests that
X.I du
Xprovides a breakdown of the disk usage by age rather than a single total.
XEach argument specifies a number of days.
X.I Du
Xwill print one column per argument showing the usage by files that many
Xdays or older.  For example, the command:
X.sp
X.RS
Xdu -c 0,7,30
X.RE
X.sp
Xwill start at the current directory and display the total usage, the usage
Xby files a week or older, and the usage of files a month or older.  The
Xfile's modification time is used to provide this breakdown.
X.IP "\-d" 10
XThis option prevents
X.I du
Xfrom descending into directories encountered.  It will count the space
Xused by the directory information, but will not examine the contents of
Xthat directory.
X.IP "\-f" 10
XThis option will prevent
X.I du
Xfrom descending down into a directory if that action requires crossing
Xonto another filesystem.
X.IP "\-h" 10
XDisplays a help summary.
X.IP "\-i" 10
XDo not accumulate subdirectories' usage into the parent directory's usage.
XFor example, if you run the USENET news software, you could perform a
Xcommand similar to:
X.sp
X.RS
Xdu -i /usr/spool/news
X.RE
X.sp
Xto determine the disk space used by each newsgroup without accumulating,
Xfor example, the
X.I comp.unix.xenix
Xnewsgroup usage in with the
X.I comp.unix
Xnewsgroup usage.
X.IP "\-l" 10
XNormally
X.I du
Xwill count a file with multiple links only once.  With this option,
X.I du
Xwill count the file each time is is encountered.
X.IP "\-r" 10
XNormally,
X.I du
Xdoes it's work silently and will not report any errors (i.e. a
X.I name
Xspecified on the command line doesn't exist or you don't have permissions
Xto check a directory).  When this option is specified, such errors will
Xbe reported.
X.RI ( Note :
XOn some systems the default will be to changed to display errors, in which
Xcase this option will have no effect.)
X.IP "\-s" 10
XPrints only a grand total for each
X.I name
Xon the command line.
X.IP "\-u" 10
XCauses multiply linked files to be skipped entirely and omitted from the
Xusage statistics.
X.\".SH SEE ALSO
X.SH BUGS
X.I Du
Xwill report files with holes in them (sparse files) incorrectly.
X.P
XAnything but a regular file or directory (e.g. special files, FIFOs, etc.)
Xwill be silently ignored.
X.P
XIf the actual filesystem blocksize is not an integral multiple of the
Xreporting blocksize
X.RB ( \-b )
Xthen roundoff errors will occur in the translation.
X.I Du
Xwill round up in this case.
X.SH AUTHOR
X.nf
XChip Rosenthal
X<chip@chinacat.Unicom.COM>
X.sp
XCopyright 1990, Unicom Systems Development.  All rights reserved.
XSee distributed README file for terms of distribution and use.
X.fi
END_OF_FILE_du.man
    size="`wc -c < du.man`"
    if test 4670 -ne "$size" ; then
	echo "du.man: extraction error - got $size chars"
    fi
fi
if test -f ngsizes -a "$1" != "-c" ; then
    echo "ngsizes: file exists - will not be overwritten"
else
    echo "x - ngsizes (file 9 of 9, 5354 chars)"
    sed -e 's/^X//' << 'END_OF_FILE_ngsizes' > ngsizes
X: '@(#) ngsizes 1.2 90/09/08 14:38:58'
X#
X# ngsizes - Generate disk usage summary for USENET newsgroups.
X#
X# Usage:
X#
X#   ngsizes [-D] [-b breakdown_list] [-t threshold]
X#
X#     -t  Specifies only groups using "threshold" or more disk blocks should
X#         be reported.  The default is defined by the "threshold" parameter
X#         below.
X#
X#     -b  Specifies how usage should be broken down versus age.  For example,
X#         saying "-b 0,7,14" will report usage in three columns:  the total
X#         usage, the usage by articles a week or older, and the usage by
X#         articles two weeks or older.  The default is defined by the
X#         "breakdown" parameter below.
X#
X#     -D  For debugging, the temporary files will be maintained.
X#
X# Site-Specific Definitions:
X#
X#   SPOOLDIR	Must point to your USENET spool directory.
X#   ACTIVE	Must point to your list of active USENET newsgroups.
X#   DU		Must point to the enhanced "du" command.
X#
X# Work Files:
X#
X#   $TMP.read	Readership statistics.
X#   $TMP.ngs	List of all newsgroups to check.
X#   $TMP.du	Disk usage for all directories in the news spool dir.
X#
X#
X# Sat Sep  8 14:34:56 1990 - Chip Rosenthal <chip@chinacat.Unicom.COM>
X#	Cleanup for distribution.
X# Tue Apr 17 21:50:58 1990 - Chip Rosenthal <chip@chinacat.Unicom.COM>
X#	Original composition.
X#
X
X# Site-specific definitions.
XSPOOLDIR=/usenet/spool/news	# Points to the local news spool directory.
XACTIVE=/usenet/lib/news/active	# Points to the local list of active newsgroups.
XDU=du				# Points to the enhanced "du" command.
X
X# Default initializations.
Xdebug=0			# set nonzero to keep temp files around
Xthreshold=0		# show newsgroups greater than this (or '0' for all)
Xbreakdown=0,1,3,5,7,15	# breakdown usage by age, one col per number days given
X
XTMP=/tmp/ngsz$$
XUSAGE="usage: $0 [-b breakdown_list] [-t threshold]"
X
Xtrap 'trap "" 0 ; rm -f $TMP.* ; exit 1' 1 2 3
Xtrap 'rm -f $TMP.* ; exit 0' 0
X
X# Crack the command line options.
Xif set -- `getopt 'Db:t:' $*` ; then
X    : getopt worked
Xelse
X    echo "$USAGE" 1>&2
X    exit 1
Xfi
Xwhile : ; do
X    case "$1" in
X	-D)  debug=1 ; shift ;;
X	-b)  breakdown="$2" ; shift ; shift ;;
X	-t)  threshold="$2" ; shift ; shift ;;
X	--)  shift ; break ;;
X	*)   echo "$USAGE" 1>&2 ; exit 1 ;;
X    esac
Xdone
Xif [ $# -ne 0 ] ; then
X    echo "$USAGE" 1>&2
X    exit 1
Xfi
X
X# If debug is enabled, setup to keep temporary files around.
Xif [ $debug -ne 0 ] ; then
X    trap '' 0 1 2 3
X    TMP=/tmp/ngsz
Xfi
X
X# Verify we can find the active file.
Xif [ ! -r $ACTIVE ] ; then
X    echo "$0: file '$ACTIVE' not found or unreadable" 1>&2
X    exit 1
Xfi
X
X# Get a count of the readers for each newsgroup.
X# Output format will be "readership_count newsgroup_name"
Xfor newsrc in `awk -F: '{ print $6 "/.newsrc" }' /etc/passwd | sort -u` ; do
X    if [ -f $newsrc ] ; then
X	sed -n -e 's/:.*//p' $newsrc
X    fi
Xdone | sort | uniq -c > $TMP.read
X
X# Extract the newsgroup names from the active file.
X# Output format will be "newsgroup_name"
Xsed -e 's/[ 	].*//' -e '/^$/d' $ACTIVE | sort -u > $TMP.ngs
X
X# Scan the spool directory for disk usage.  Convert the newsgroup pathname
X# to a newsgroup name, and move it to the first field on the line.
X# Output format will be "newsgroup_name usage usage ..."
Xif [ $debug -ne 0 -a -f $TMP.du ] ; then
X    : suppress scan for debugging
Xelse
X    $DU -ilr -c "$breakdown" $SPOOLDIR				\
X	| sed							\
X	    -e 's/^\(.*\)	\([^	]*\)$/\2	\1/'	\
X	    -e "s!$SPOOLDIR/!!"					\
X	    -e "s!/!.!g"					\
X	| sort -u						\
X	    > $TMP.du
Xfi
X
X
X# Generate the report.
X(
X    echo "BREAKDOWN $breakdown" | sed -e 's/,/ /g'
X    echo "THRESHOLD $threshold"
X    sed -e 's/^/READERS /' $TMP.read
X    join $TMP.du $TMP.ngs | sort -rn +1 | sed -e 's/^/NEWSGROUP /'
X) | awk '
X
XBEGIN {
X    LINE_WIDTH = 79	# maximum length of a line
X    NG_WIDTH = 26	# width of field to print newsgroup in
X    READR_WIDTH = 4	# width of field to print number of readers in
X    FRONT_FMT = "%-" NG_WIDTH "." NG_WIDTH "s" "%" READR_WIDTH "s"
X}
X
X# Record "BREAKDOWN n1 n2 ..."
X#   Defines the format for the newsgroup usage lines.  Each "n" corresponds
X#   to one column in the newsgroup usage line, and specifies the age of
X#   articles which consume this amount of disk space.
X$1 == "BREAKDOWN" {
X    num_breakdn = NF - 1
X    FIELD_WIDTH = ( LINE_WIDTH - (NG_WIDTH+READR_WIDTH) ) / num_breakdn
X    if ( FIELD_WIDTH > 8 )
X	FIELD_WIDTH = 8
X    FIELD_FMT = "%" FIELD_WIDTH "s"
X    printf(FRONT_FMT,"newsgroup","read")
X    for ( i = 0 ; i < num_breakdn ; ++i )
X	printf(FIELD_FMT,sprintf("%ddays",$(i+2)))
X    printf("\n")
X    next
X}
X
X# Record "THRESHOLD n"
X#   Indicates we only want to see newsgroups using "n" or more blocks.
X$1 == "THRESHOLD" {
X    threshold = $2
X    next
X}
X
X# Record "READERS n ng"
X#   Indicates that newsgroup "ng" has "n" readers.
X$1 == "READERS" {
X    num_readers[$3] = $2
X    next
X}
X
X# Record "NEWSGROUP ng n1 n2 ..."
X#   Indicates the disk usage of newsgroup "ng".  Each "n" specifies the
X#   diskspace used by articles "ndays" or older, where "ndays" is defined
X#   by the BREAKDOWN record.
X$1 == "NEWSGROUP" {
X    if ( $3 >= threshold ) {
X	if ( num_readers[$2] == "" )
X	    num_readers[$2] = 0
X	printf(FRONT_FMT,$2,num_readers[$2])
X	for ( i = 0 ; i < num_breakdn ; ++i )
X	    printf(FIELD_FMT,$(i+3))
X	printf("\n")
X    }
X    next
X}
X
X{ printf("ngsizes - bad line '%s'\n", $0) | "cat 1>&2" }
X
X'
X
Xexit 0
X
END_OF_FILE_ngsizes
    size="`wc -c < ngsizes`"
    if test 5354 -ne "$size" ; then
	echo "ngsizes: extraction error - got $size chars"
    fi
fi
echo "done - 9 files extracted"
exit 0
-- 
Chip Rosenthal  <chip@chinacat.Unicom.COM>
Unicom Systems Development, 512-482-8260 
Our motto is:  We never say, "But it works with DOS."