[comp.sources.misc] v02i005: File System Analyzer Tool

mjy@sdti.UUCP (Michael J. Young) (01/15/88)

Comp.Sources.Misc: Volume 2, Issue 5

Submitted-By: Michael J. Young <mjy@sdti.UUCP>

Archive-Name: sys5-fs-analysis


Comp.Sources.Misc: Volume 2, Issue 5
Submitted-By: Michael J. Young <mjy@sdti.UUCP>
Archive-Name: sys5-fs-analysis

I've tried posting this to comp.sources.unix a number of times (since
October!), but I seem to have discovered a black hole between here and that
newsgroup.  Since I'm starting to get gentle reminders that I'd promised
this awhile ago, I decided to punt and try posting here.

This is a tool that I wrote to determine file system fragmentation on my
Microport box.  I mentioned it a few months ago in comp.unix.wizards, and
got a lot of requests for the source.  It's not very elegant, and it's
specific to System V, but it's functional.

I'd really like to see it ported to other systems (I only have access to
System V).  If anyone has lots of spare time(!)  and wants to port it, there
are some notes in the README file for things to watch out for.  Please send
me your changes so I can incorporate them into my version.  If you are
really bored, feel free to add an in-place disk-compression capability! :-)

--------------------- cut here -------- cut here --------------------------
#! /bin/sh
# This is a shell archive.  Remove anything before this line, then unpack
# it by saving it into a file and typing "sh file".  To overwrite existing
# files, type "sh file -c".  You can also feed this as standard input via
# unshar, or by typing "sh <file", e.g..  If this archive is complete, you
# will see the following message at the end:
#		"End of archive 1 (of 1)."
# Contents:  README fsanalyze.8 fsanalyze.c
# Wrapped by mjy@sdti on Thu Jan  7 15:46:01 1988
PATH=/bin:/usr/bin:/usr/ucb ; export PATH
if test -f README -a "${1}" != "-c" ; then 
  echo shar: Will not over-write existing file \"README\"
else
echo shar: Extracting \"README\" \(7915 characters\)
sed "s/^X//" >README <<'END_OF_README'
XFSANALYZE - System V File System Analyzer tool - v2.04, 7 Januaray 1988
X
XAuthor   : Michael J. Young
XUSmail   : Software Development Technologies, Inc.
X           375 Dutton Rd
X           Sudbury MA  01776
XUUCP     : {decvax|harvard|linus|mit-eddie}!necntc!necis!mrst!sdti!mjy
XInternet : mjy%sdti.uucp@harvard.harvard.edu
X
X   =========================================================================
X   Note : This program has been placed in the public domain to permit
X   unrestricted distribution and use.  I have placed no copyright on it, but
X   I request that you keep me informed about any enhancements and bug fixes
X   you make so I can keep an up-to-date copy for further distribution.
X   =========================================================================
X
X   Fsanalyze is a simple tool that estimates file system fragmentation.  It
X   accomplishes this by scanning the data blocks for each i-node in the 
X   file system, looking for block numbers that are out of sequence.  In
X   effect, it is counting the number of disk seeks required to read the
X   entire file in sequence.  Fragmentation is then computed as the ratio
X   of actual "seeks" to the potential number of "seeks" if the file were
X   completely fragmented.
X
X   Fsanalyze also provides statistics regarding the number (and identity) of
X   files that are very large, and excessively large directories.
X   
X   Excessively large directories are directories that are over 5120 bytes
X   long (320 entries).  Directories larger than this require data block
X   indirection, making file searches very inefficient.
X
X   After the general file system statistics are displayed, fsanalyze lists
X   the 10 most fragmented i-nodes in the file system.  
X   The 10 most fragmented files are listed in decreasing order of 
X   fragmentation based on the absolute number of fragments.  For example,
X   a 100-block file that contains 40 individual fragments is 39.39%
X   fragmented (39 seeks / 99 potential seeks), but is listed before
X   a 2-block file that contains 2 fragments (100% fragmented).  Thus, larger
X   fragmented files (which have a greater effect on file system performance)
X   are listed before small files.
X
XRevision History:
X
X   Version 2.0 is a major rewrite of fsanalyze that adds the following new
X   features:
X       - The ability to analyze individual files
X       - Display of the 10 most fragmented files in a file system
X       - Enhanced error checking on the file system argument
X
X   Version 2.01 contains a minor modification in which fsanalyze executes
X   /etc/fsstat to determine the health of the file system before analyzing
X   it.
X
X   Version 2.02 contains a minor modification to print out a warning message
X   if the file system being analyzed is currently mounted.
X
X   Version 2.03 contains a fix for a minor bug in which the size of the
X   volume data block size would be printed out incorrectly for large
X   file systems.
X
X   Version 2.04 incorporates the library function l3tol to access inode
X   block numbers.
X
XInstallation:
X
X   This program is so simple that I didn't bother to create a Makefile.  To
X   build fsanalyze the steps are:
X      cc -O -o fsanalyze fsanalyze.c
X      mv fsanalyze /bin
X   Where fsanalyze is installed doesn't really matter, but I would recommend
X   placing it somewhere in the root filesystem, which is always mounted.  I
X   typically run fsanalyze during my backup procedure, while my other
X   filesystems are unmounted.
X
XUsage:
X   fsanalyze [-flags] special [file [...]]
X
X   If the optional filename arguments are missing, the entire file system
X   is analyzed.  If present, the specified files are analyzed and reported
X   individually.
X
X   [flags] include the following:
X     d   display i-node numbers as they are examined
X     e   report file size inconsistencies - the inode numbers are reported
X         for files where the file size and number of data blocks are
X         inconsistent.
X     i   report double and triple indirection - the inode numbers are
X         reported for files that contain double and/or triple indirection.
X     o   overrides error checking on file system.  Use this flag if the
X         file system you are analyzing is damaged.  Note that fsanalyze
X         may give erroneous results if used on a damaged file system, but
X         the file system itself will not be affected.
X
X   Example:
X     fsanalyze /dev/dsk/0s2	/* analyzes an entire file system */
X     fsanalyze /dev/dsk/0s2 *	/* analyzes all files in the current
X                                 * directory of the file system */
X
X   Since fsanalyze uses the superblock info ON THE DISK, more accurate
X   results will be returned fsanalyze is run on an unmounted, or read-only
X   mounted file system immediately after a sync(2).
X
X   Note that fsanalyze will work with 512- or 1024-byte filesystems.  In
X   filesystems with 1024-byte blocking-factors, free-blocks, etc., are
X   reported as 1024-byte blocks (unlike df(1) and du(1)).  Therefore, the total
X   number of free blocks in your file system will be reported as half the
X   value reported by df(1).  I prefer it this way.  If you want it the
X   other way, it requires a simple change to print_report().
X
X   Since fsanalyze does its work the old fashioned way (brute-force), it
X   must scan through the file system inode by inode.  It therefore takes
X   about as long as fsck to do its work.  Be patient.
X
XBugs:
X   Please report any bugs (and possible fixes) to me, so I can keep my
X   source up-to-date.  I would also like to make fsanalyze work on other
X   unix systems, but I don't have access to anything but my little 
X   uport.  If you can help me port it to BSD or anything else, I would
X   appreciate it.
X
XPorting fsanalyze to other systems:
X   Fsanalyze takes its information on the structure of the file system
X   from /usr/include/sys/filsys.h and /usr/include/sys/ino.h.  As long
X   as these files (or equivalent) are present on your system, porting
X   should be straightforward.  When looking for the equivalent of these
X   files on your system, be sure to get the disk-based versions, rather
X   than the in-memory structures -- they are different.  In System V
X   derivatives, these structures should be documented as fs(4 or 5) and
X   inode(4 or 5), on BSD systems and V7 systems as filsys(5), and on
X   XENIX 3.0 and 5.0 as filesystem(F).
X
X   Fsanalyze uses fsstat(1M) to determine file system integrity.  I don't
X   think this program is available in other versions of Unix.  If your
X   system doesn't have it (or anything like it), you will have to rewrite
X   check_fs().  The kinds of things to check when testing for file system
X   integrity include:
X	1.  Make sure the device is block structured.
X	2.  Determine whether or not the device is mounted.
X	3.  Check s_state in the filsys structure to see if it is in a
X	    known state.
X	4.  Anying else you can think of.
X   I used fsstat because it seems to be more robust than anything I
X   could come up with.  It also appears to use undocumented information
X   to determine file system status.
X
X   fsstat(1M) returns 0 if the file system is ok, 1 if it needs to be
X   checked, 2 if it is mounted, and 3 for other errors.  Unfortunately,
X   the exit status appears to be byte-swapped when fsanalyze gets it.
X   I'm not sure if this is a bug in system(3S), or my use of it, but
X   be careful when porting to your system.
X  
X   I attempted to use types defined in /usr/include/sys/types.h wherever
X   possible to ensure portability.  I don't think I made any assumptions
X   about sizeof(int) == sizeof(long) == sizeof(char *), so there should
X   be no problems there.
X
X   Please try to make changes to the source using #defines and #ifdefs
X   wherever possible, and please email me the changes you make along with
X   a description of the system you ported it to (and problems encountered),
X   so I can merge all the changes into a single copy.
X
X   Thanks and good luck!
END_OF_README
if test 7915 -ne `wc -c <README`; then
    echo shar: \"README\" unpacked with wrong size!
fi
# end of overwriting check
fi
if test -f fsanalyze.8 -a "${1}" != "-c" ; then 
  echo shar: Will not over-write existing file \"fsanalyze.8\"
else
echo shar: Extracting \"fsanalyze.8\" \(3441 characters\)
sed "s/^X//" >fsanalyze.8 <<'END_OF_fsanalyze.8'
X.TH FSANALYZE 8
X.ad b
X.SH NAME
Xfsanalyze \- a file system analyzer tool
X.SH SYNOPSIS
X.B fsanalyze
X[
X.B \-deio
X]
X.B special
X[
X.B files
X]
X.SH DESCRIPTION
X.I Fsanalyze
Xis a simple tool that estimates file or file system fragmentation.  It accomplishes
Xthis by scanning the data blocks of each inode looking for block numbers that
Xare out of sequence.  In effect, it is counting the number of disk seeks
Xrequired to read a file sequentially.  Fragmentation is then computed as the
Xratio of actual "seeks" to the potential number "seeks" which would be
Xrequired if the file were completely fragmented.
X.LP
XIf the optional \fBfiles\fR argument is omitted,
X.I fsanalyze
Xwill analyze the entire file system specified by the \fBspecial\fR argument.
X\fBSpecial\fR must be a block-oriented file system device.
X.I Fsanalyze
Xreports various statistics regarding file system usage and data-block
Xindirection, plus a list of the 10 most fragmented inodes in the file system.
XThe 10 most fragmented files are listed in decreasing order of fragmentation
Xbased on the absolute number of fragments.  For example a 100-block file
Xthat contains 40 individual fragments is 39.39% fragmented (39 seeks / 99
Xpotential seeks), but is listed before a 2-block file that contains 2
Xfragments (100% fragmented).
X.LP
XIf the \fBfiles\fR argument is present,
X.I fsanalyze
Xwill report the fragmentation of the designated files only.
X.LP
XBefore performing any analysis,
X.I fsanalyze
Xchecks the file system integrity by invoking
X.I fsstat (1M).
XIf the file system needs to be checked,
X.I fsanalyze
Xterminates with a message to that effect.  If a non-root file system is
Xmounted, a warning message is displayed, but analysis continues.  When
Xused on a mounted file system, it is recommended that a
X.I sync (1)
Xbe performed immediately prior to running
X.I fsanalyze .
XIf
X.I sync (1)
Xis not performed, the file system will not be damaged, but erroneous
Xstatistics may be reported.
X.SH OPTIONS
X.IP \fB\-d\fR
Xdisplay inode numbers as they are examined.  This flag makes it easy to
Xchart the progress of fsanalyze through the file system.  Used mainly for
Xdebugging.
X.IP \fB\-e\fR
Xreport file size inconsistencies - the inode numbers are reported for files
Xwhere the file size and number of data blocks are inconsistent.  This
Xoption provides the same information as
X.I fsck (1)
Xphase 1.
X.IP \fB\-i\fR
Xreport double and triple indirection - the inode numbers are reported for
Xfiles that contain double and/or triple data-block indirection.
X.IP \fB\-o\fR
Xoverrides file system integrity checks.  The file system will be analyzed
Xeven if
X.I fsstat (1M)
Xreports that it is damaged.  Note that
X.I fsanalyze
Xmay give erroneous results if used on a damaged file system, but the file
Xsystem itself will not be affected.
X.SH EXAMPLES
Xfsanalyze /dev/dsk/0s2    # analyze file system
X.br
Xfsanalyze /dev/dsk/0s2 *  # analyze files in current dir
X.SH FILES
X/usr/include/sys/filesys.h	super block structure
X.br
X/usr/include/sys/ino.h		disk inode structure
X.br
X/usr/include/sys/param.h	system parameters
X.br
X/usr/include/sys/types.h	system type definitions
X.SH BUGS
X.I Fsanalyze
Xis supported by System V Release 2.  It has not yet been ported to any
Xother version of Unix.
X.SH "SEE ALSO"
Xfsstat(1M), fsck(1M), fsdb(1M)
X.SH AUTHOR
XMichael J. Young
X.br
X{decvax|harvard|linus|mit-eddie}!necntc!necis!mrst!sdti!mjy
X.br
XInternet : mjy%sdti.uucp@harvard.harvard.edu
X.SH VERSION
Xv2.04 \- 7 January 1988
END_OF_fsanalyze.8
if test 3441 -ne `wc -c <fsanalyze.8`; then
    echo shar: \"fsanalyze.8\" unpacked with wrong size!
fi
# end of overwriting check
fi
if test -f fsanalyze.c -a "${1}" != "-c" ; then 
  echo shar: Will not over-write existing file \"fsanalyze.c\"
else
echo shar: Extracting \"fsanalyze.c\" \(25225 characters\)
sed "s/^X//" >fsanalyze.c <<'END_OF_fsanalyze.c'
X/*
X * fsanalyze.c - file system analyzer - v2.04; 7 January 1988
X *
X * Author   : Michael J. Young
X * USmail   : Software Development Technologies, Inc.
X *            375 Dutton Rd
X *            Sudbury MA 01776
X * UUCP     : {decvax|harvard|linus|mit-eddie}!necntc!necis!mrst!sdti!mjy
X * Internet : mjy%sdti.uucp@harvard.harvard.edu
X *
X * =========================================================================
X * Note : This program has been placed in the public domain to permit
X * unrestricted distribution and use.  I have placed no copyright on it, but
X * I request that you keep me informed about any enhancements and bug fixes
X * you make so I can keep an up-to-date copy for further distribution.
X * =========================================================================
X *
X * fsanalyze is a simple tool that estimates file system fragmentation.  It
X * accomplishes this by scanning the data blocks for each i-node in the 
X * file system, looking for block numbers that are out of sequence.  
X * Fragmentation is then computed as the ratio of out-of-sequence blocks to
X * total data blocks.
X * 
X * fsanalyze also provides statistics regarding the number (and identity) of
X * files that are very large, and especially excessively large directories 
X * (which are very inefficient).
X * 
X * To build fsanalyze the steps are:
X *       cc -O -o fsanalyze fsanalyze.c
X *       mv fsanalyze /bin
X *
X * Usage:
X *    fsanalyze [-flags] special [file] ...
X *
X *    where [flags] include the following:
X *      d   display i-node numbers as they are examined
X *      e   report file size inconsistencies
X *      i   report inodes numbers with double and triple indirection
X *	o   override error checking on file system superblock
X *
X *    If the optional file argument(s) are present, only the specified
X *    individual files on the file system are analyzed.  If absent, the
X *    entire file system is analyzed.
X *
X *    Example:
X *      fsanalyze /dev/dsk/0s2
X *
X *    Since fsanalyze uses the superblock info ON THE DISK, more accurate
X *    results will be returned if sync(1) is executed immediately prior to
X *    fsanalyze.
X */
X
X/*
X * Modification History:
X *
X *    Date       Author                   Description
X * -----------  --------  -----------------------------------------------
X * 28 Jul 1987    MJY       Originated
X *  5 Oct 1987    MJY       Capability to analyze individual files,
X *			    Added error checking to file system argument,
X *			    Added -o flag
X *			    Prints out volume and file system name in summary
X * 12 Oct 1987    MJY       Use /etc/fsstat to do file system validity
X *                          checking
X *  9 Nov 1987    MJY       print out warning if file system is mounted
X * 12 Nov 1987    MJY       Volume size statistics now long instead of int
X *  7 Jan 1988    MJY       Modified blk_no() to use l3tol()
X */
X
X
X# include <stdio.h>
X# include <sys/types.h>
X# include <sys/ino.h>
X# include <sys/param.h>
X# include <sys/filsys.h>
X# include <sys/stat.h>
X
X# define BLK_SIZE	512		/* block size */
X# define IBLK 		2		/* block number of first i-node */
X
X# define I_BLOCK_FACTOR	32		/* number of i-nodes per block */
X
X/*
X * file mode definitions
X */
X# define FILE_TYPE(a) 	((a)->di_mode & S_IFMT)
X# define IS_SPECIAL(a)	(((a) & S_IFMT == S_IFBLK) || ((a) & S_IFMT == S_IFCHR) || ((a) & S_IFMT == S_IFIFO))
X
X/*
X * basic definitions
X */
X# define FALSE	0
X# define TRUE	1
X
Xtypedef int boolean;
X
X/*
X * per-file statistics structure
X */
Xstruct file_data {
X	int inode;			/* i-node number */
X	long total_blocks;		/* total blocks in file (incl 
X					 * indirect blocks */
X	long data_blocks;		/* total data blocks in file */
X	long potential_seeks;		/* number of potential seeks in file */
X	long seeks;			/* actual seeks in file */
X	float fragm;			/* fragmentation (seeks/pot.seeks) */
X	};
X
XFILE *fsys               = NULL;	/* file system under test */
Xchar *special            = NULL;	/* file system device name */
X
X/*
X * file system static data
X */
Xstruct filsys super      = {0};		/* holds file system super block */
Xint fs_type              = 1;		/* file system logical block size
X					 * (in 512 byte units) */
Xlong num_inodes          = 0;		/* number of i-nodes in file system */
X
X/*
X * calculated global statistics
X */
Xlong blocks              = 0;		/* running block count */
Xlong free_inodes         = 0;		/* number of unused i-nodes */
Xlong potential_seeks     = 0;		/* potential number of disk seeks
X					 * during sequential access of a 
X					 * file */
Xlong seeks               = 0;		/* actual number of disk seeks
X					 * required during sequential access
X					 * of a file */
Xlong indirects           = 0;		/* number of files w/ more than
X					 * 10 data blocks */
Xlong double_indirects    = 0;		/* number of files w/ more than
X					/* one level of indirection */
Xlong triple_indirects    = 0;		/* number of files w/ more than
X					 * two levels of indirection */
Xint big_directories      = 0;		/* number of directories with
X					 * indirection */
Xint num_directories      = 0;		/* number of directories */
Xint linked_files         = 0;		/* number of multiply-linked files */
Xint size_errors          = 0;		/* number of file size discrepancies */
X
X
Xstruct file_data file_log[10] = {0};		/* 10 worst offenders */
X
X
X/*
X * fsanalyze command-line flags
X */
Xboolean report_indirects = FALSE;	/* command line option -- report
X					 * files with indirect data blocks */
Xboolean report_errors    = FALSE;	/* command line option -- report
X					 * file size discrepancies */
Xboolean debug            = FALSE;	/* report i-node #s as they are
X					 * examined */
Xboolean override	 = FALSE;	/* override bad fs test */
X
X
X/*
X * interface to system error messages
X */
Xextern int errno;
Xextern char *sys_errlist[];
Xextern int sys_nerr;
X
X/*
X * usage : prints out a short message describing command usage.  This
X * function does not return.
X */
Xvoid usage(){
X	printf ("File System analyzer - v2.01\n");
X	printf ("Usage:\n   fsanalyze [-[deio]] special [file] ...\n");
X	printf ("\n\tIf the [file] argument(s) are missing, the entire\n");
X	printf ("\tfile system is scanned.  Otherwise, only the specified\n");
X	printf ("\tfiles are examined.  Valid flag are:\n\n");
X	printf ("\td\tdisplay i-node numbers as they are examined\n");
X	printf ("\te\treport file size inconsistencies\n");
X	printf ("\ti\treport data block double and triple indirection\n");
X	printf ("\to\toverride error checking on file system argument\n");
X	exit(1);
X	}
X
X/*
X * check_fs : checks the file system to be sure it is not in need of
X * checking.  If the file system is damaged, the function displays a
X * message from fsstat, then returns FALSE.  Othersize, returns TRUE.
X */
Xboolean check_fs (special)
Xchar *special;
X{
X	char buffer[256];			/* buffer to hold cmd */
X	int fstat;				/* exit status of fsstat */
X
X	sprintf (buffer, "/etc/fsstat %s 2>/dev/null", special);
X	fstat = system (buffer);
X	if (fstat != 0 && !override){
X		if (fstat == 512 /* why is this byte-swapped? */){
X			fprintf (stderr, "warning: file system is mounted\n");
X			}
X		else {
X			/*
X			 * run fsstat again to get error message (this is a kludge,
X			 * but I hope more portable that having fsanalyze do the
X			 * analysis itself)
X			 */
X			sprintf (buffer, "/etc/fsstat %s", special);
X			system(buffer);
X			return (FALSE);
X			}
X		}
X	return (TRUE);
X	}
X
X/*
X * error : prints out a formatted error string to stderr, followed if
X * possible by an appropriate system error message.  Control is then
X * returned to the system with an error status.  This function does not
X * return.
X */
Xvoid error (err, str, arg1, arg2)
Xint err;			/* value of errno at time of call */
Xchar *str;			/* error string */
Xchar *arg1, *arg2;		/* additional printf arguments, if any */
X{
X	fprintf (stderr, str, arg1, arg2);
X	if (err <= sys_nerr && err > 0)
X		fprintf (stderr, "%s\n", sys_errlist[err]);
X	else
X		fprintf (stderr, "unknown error : %d\n", err);
X	exit(1);			/* exit with error status */
X	}
X
X/*
X * init_stats : initializes per-file statistics structure
X */
Xvoid init_stats (data, inode)
Xstruct file_data *data;				/* file stats to init */
Xint inode;					/* i-node number */
X{
X	data->inode = inode;
X	data->total_blocks = data->data_blocks = 0;
X	data->potential_seeks = data->seeks = 0;
X	data->fragm = 0.0;
X	}
X
X/*
X * init : parses command line flags and the file system device name, then
X * reads the file system super block.  init returns the number of the
X * parameter AFTER the file system device name.  All subsequent parameters
X * are assumed to be specific files to be tested.
X */
Xint init (argc, argv)
Xint argc;
Xchar *argv[];
X{
X	int i;					/* loop counter */
X	char *cp;				/* cmd-line flag pointer */
X	boolean special_found = FALSE;		/* TRUE = found special arg */
X	
X	for (i = 1; i < argc && !special_found; i++){
X		cp = argv[i];
X		if (*cp == '-'){
X			while (*++cp){
X				switch (*cp){
X
X						/* report i-nodes with
X						 * one or more levels of
X						 * indirection */
X					case 'i':
X						report_indirects = TRUE;
X						break;
X
X						/* report file size errors */
X					case 'e':
X						report_errors = TRUE;
X						break;
X
X						/* debugging flag */
X					case 'd':
X						debug = TRUE;
X						break;
X
X						/* override bad fs test */
X					case 'o':
X						override = TRUE;
X						break;
X					default:
X						fprintf (stderr, "illegal option : %c\n", *cp);
X						usage();
X					}
X				}
X			}
X		else {
X			special = argv[i];
X			special_found = TRUE;
X			}
X		}
X	if (special == NULL)usage();		/* no fs parameter */
X
X	/*
X	 * check for good file system (let fsstat do the dirty work!)
X	 */
X	if (check_fs (special)){
X
X		/*
X		 * open file system and read the super block
X		 */
X		if ((fsys=fopen (special, "r")) == NULL){
X			error (errno, "error opening \"%s\"\n", special);
X			/* NOTREACHED */
X			}
X		if (fseek (fsys, 512L, 0)){
X			error (errno, "error seeking superblock");
X			/* NOTREACHED */
X			}
X		if (fread (&super, sizeof (struct filsys), 1, fsys) != 1){
X			error (errno, "error reading superblock");
X			/* NOTREACHED */
X			}
X
X		num_inodes = (super.s_isize-2) * 16;
X		if (super.s_magic != FsMAGIC)
X			fs_type = Fs1b;
X		else fs_type = super.s_type;
X
X		return i;		/* return # of next argument to be processed */
X		}
X	else {
X		exit(1);		/* bad file system, error status */
X		}
X	}
X
X/*
X * blk_no : given a pointer to a 3-byte binary number, returns a (long)
X * block number.  Used to access the first 10 data block numbers of an
X * i-node.  The function l3tol is used for portability.
X */
Xdaddr_t blk_no (off)
Xunsigned char off[];				/* 3-byte offset */
X{
X	extern void l3tol();
X	daddr_t temp = 0;
X	int n;
X
X	l3tol (&temp, off, 1);
X	return temp;
X	}
X
X/*
X * get_inodes : given an initial i-node number, reads a block of i-nodes
X * into an i-node array.
X */
Xvoid get_inodes (in, inp, num)
Xint in;						/* inode number */
Xint num;					/* # inodes to get */
Xstruct dinode *inp;				/* buffer to hold info */
X{
X	long position;				/* computed position of the
X						 * first requested i-node */
X
X	position = (IBLK * BLK_SIZE * fs_type) + 
X		   (sizeof (struct dinode) * ((long)in-1));
X
X	if (fseek (fsys, position, 0)){
X		error (errno, "\nerror seeking inode %d, pos = %ld\n", in, position);
X		/* NOTREACHED */
X		}
X	else {
X		if (fread (inp, sizeof (struct dinode), num, fsys) != num){
X			error (errno, "\nerror reading inode %d\n", in);
X			/* NOTREACHED */
X			}
X		}
X	}
X
X/*
X * check_indirects : scans a block containing data block numbers, looking
X * for block numbers that are not sequential.
X */
Xdaddr_t check_indirects (block, cur_pos, data)
Xdaddr_t block;					/* indirect block to check */
Xdaddr_t cur_pos;				/* current block offset */
Xstruct file_data *data;				/* current file statistics */
X{
X	daddr_t indirect_blk[256];		/* holds an indirect block */
X	int num_blocks;				/* number of data blocks in
X						 * an indirect block */
X	daddr_t pos;				/* current data block */
X	daddr_t new_pos;			/* next data block */
X	int i;					/* loop counter */
X
X	num_blocks = 128 * fs_type;
X	data->total_blocks++;
X
X	/*
X	 * get the indirect block
X	 */
X	if (fseek (fsys, block * 512L * fs_type, 0)){
X		error (errno, "\nerror seeking indirect block %ld\n", block);
X		/* NOTREACHED */
X		}
X	if (fread (indirect_blk, sizeof (daddr_t), num_blocks, fsys) != num_blocks){
X		error (errno, "\nerror reading indirect block %ld\n", block);
X		/* NOTREACHED */
X		}
X	pos = cur_pos;
X
X	/*
X	 * scan the data blocks looking for numbers out of sequence
X	 */
X	for (i = 0; i < num_blocks; i++){
X		new_pos = indirect_blk [i];
X		if (new_pos == 0){
X			return pos;
X			}
X		data->data_blocks++;
X		data->total_blocks++;
X		data->potential_seeks++;
X		if (new_pos != pos + 1){
X			data->seeks++;
X			}
X		pos = new_pos;
X		}
X	return pos;
X	}
X
X/*
X * check_double_indirects : scans a block containing a list of indirect
X * blocks, checking for data block numbers that are out of sequence.
X */
Xdaddr_t check_double_indirects (block, cur_pos, data)
Xdaddr_t block;					/* indirect block to check */
Xdaddr_t cur_pos;				/* current block offset */
Xstruct file_data *data;				/* current file statistics */
X{
X	daddr_t dindirect_blk[256];		/* holds a double-indirect
X						 * block */
X	int i;					/* loop counter */
X	int num_blocks;				/* number of indirect blocks
X						 * in a d-i block */
X
X	num_blocks = 128 * fs_type;
X	data->total_blocks++;
X
X	/*
X	 * the double-indirect block itself should be in sequence with the
X	 * data blocks
X	 */
X	data->potential_seeks++;
X	if (block != cur_pos + 1){
X		data->seeks++;
X		}
X
X	/*
X	 * get the d-i block
X	 */
X	if (fseek (fsys, block * 512L * fs_type, 0)){
X		error (errno, "\nerror seeking double indirect block %ld\n",
X			 block);
X		/* NOTREACHED */
X		}
X	if (fread (dindirect_blk, sizeof (daddr_t), num_blocks, fsys) != num_blocks){
X		error (errno, "\nerror reading double indirect block %ld\n",
X			 block);
X		/* NOTREACHED */
X		}
X
X	/*
X	 * scan through the d-i block
X	 */
X	for (i = 0; i < num_blocks; i++){
X		if (dindirect_blk[i] == 0){
X			break;
X			}
X		cur_pos = check_indirects (dindirect_blk[i],
X					   block,
X					   data);
X		}
X	return cur_pos;
X	}
X
X/*
X * check_triple_indirects : scans a block containing a list of double
X * indirect blocks, looking for data block numbers that are out of sequence.
X */
Xdaddr_t check_triple_indirects (block, cur_pos, data)
Xdaddr_t block;					/* indirect block to check */
Xdaddr_t cur_pos;				/* current block offset */
Xstruct file_data *data;				/* current file statistics */
X{
X	daddr_t tindirect_blk[256];		/* holds a triple-indirect
X						 * block */
X	int i;					/* loop counter */
X	int num_blocks;				/* number of double-indirect
X						 * blocks in a triple-i blk */
X
X
X	num_blocks = 128 * fs_type;
X	data->total_blocks++;
X
X	/*
X	 * the triple-indirect block itself should be in sequence with the
X	 * data blocks
X	 */
X	data->potential_seeks++;
X	if (block != cur_pos + 1){
X		data->seeks++;
X		}
X
X	/*
X	 * get the t-i block
X	 */
X	if (fseek (fsys, block * 512L * fs_type, 0)){
X		error (errno, "\nerror seeking triple indirect block %ld\n",
X			 block);
X		/* NOTREACHED */
X		}
X	if (fread (tindirect_blk, sizeof (daddr_t), num_blocks, fsys) != num_blocks){
X		error (errno, "\nerror reading triple indirect block %ld\n",
X			 block);
X		/* NOTREACHED */
X		}
X
X	/*
X	 * scan through the t-i block
X	 */
X	for (i = 0; i < num_blocks; i++){
X		if (tindirect_blk[i] == 0){
X			break;
X			}
X		cur_pos = check_double_indirects (tindirect_blk[i],
X						  block,
X						  data);
X		}
X	return cur_pos;
X	}
X
X/*
X * check_file : scans the data block numbers of an i-node, looking for
X * block numbers that are out of sequence, and would thus result in excess
X * track-to-track seeking.  This function also checks directory files for
X * indirection (more than 10 data blocks), and performs a simple consistency
X * check on all file sizes
X */
Xvoid check_file (inode, inode_number, data)
Xstruct dinode *inode;				/* inode info structure */
Xint inode_number;				/* inode number to be
X						 * checked */
Xstruct file_data *data;				/* current file statistics */
X{
X	daddr_t pos;				/* current block */
X	daddr_t new_pos;			/* next block in file */
X	int i;					/* loop counter */
X	long file_size;				/* file size computed by
X						 * actual byte count */
X
X	pos = blk_no (&inode->di_addr[0]);	/* first data block */
X
X	if (inode->di_size == 0 || pos == 0)return; /* ignore 0-size files */
X
X	data->data_blocks = data->total_blocks = 1;
X
X	/*
X	 * do some simple-minded statistics gathering
X	 */
X	if (FILE_TYPE (inode) == S_IFDIR){ /* got a directory */
X		num_directories++;
X		}
X	if (inode->di_nlink > 1){		/* multi-linked files */
X		linked_files++;
X		}
X
X	/*
X	 * scan the data blocks looking for numbers out of sequence
X	 */
X	for (i = 1; i < 10; i++){
X		new_pos = blk_no (&inode->di_addr[i*3]);
X		if (new_pos == 0){		/* end of file */
X			break;
X			}
X		data->data_blocks++;
X		data->total_blocks++;
X		data->potential_seeks++;
X		if (new_pos != pos + 1){	/* out of sequence ? */
X			data->seeks++;
X			}
X		pos = new_pos;
X		}
X
X	/*
X	 * block 10, if non-zero, is the number of the block which contains
X	 * the next (128 * fs_type) data block numbers.  It should also be
X	 * in sequence with the data blocks.  Block 10 is called an
X	 * "indirect" block.
X	 */
X	if (blk_no (&inode->di_addr[10*3])){
X		indirects++;
X
X		/*
X		 * if a directory contains indirection, it is too large for
X		 * efficient access.  Report it.
X		 */
X		if (FILE_TYPE (inode) == S_IFDIR){
X			printf ("inode %d is a large directory\n", inode_number);
X			big_directories++;
X			}
X		pos = check_indirects (blk_no (&inode->di_addr[10*3]),
X				       pos,
X				       data);
X		}
X
X	/*
X	 * block 11, if non-zero, is the number of the block which contains
X	 * the next (128 * fs_type) INDIRECT block numbers.  It should also be
X	 * in sequence with the data blocks.  Block 11 is called a "double-
X	 * indirect" block.
X	 */
X	if (blk_no (&inode->di_addr[11*3])){
X		double_indirects++;
X		if (report_indirects){
X			printf ("double indirection : %d\n", inode_number);
X			}
X		pos = check_double_indirects (blk_no (&inode->di_addr[11*3]),
X					      pos,
X					      data);
X		}
X
X	/*
X	 * block 12, if non-zero, is the number of the block which contains
X	 * the next (128 * fs_type) DOUBLE-INDIRECT block numbers.  It should
X	 * also be in sequence with the data blocks.  Block 12 is called a
X	 * "triple-indirect" block.
X	 */
X	if (blk_no (&inode->di_addr[12*3])){
X		triple_indirects++;
X		if (report_indirects){
X			printf ("triple indirection : %d\n", inode_number);
X			}
X		pos = check_triple_indirects (blk_no (&inode->di_addr[12*3]),
X					      pos,
X					      data);
X		}
X
X	/*
X	 * do a simple check to detect possible file-size errors (a la
X	 * fsck phase 1)
X	 */
X	file_size = (inode->di_size + (512 * fs_type - 1)) / (512 * fs_type);
X	if (file_size != data->data_blocks){
X		size_errors++;
X		if (report_errors){
X			printf ("inode %d, inconsistent file size : actual blocks = %ld, computed = %ld (%ld bytes)\n",
X				inode_number, data->data_blocks, 
X				file_size, inode->di_size);
X			}
X		}
X	}
X
X/*
X * log_stats : updates global statistics based on the current file statistics.
X * The current file is then checked to see if it qualifies as one of the 10
X * worst offenders (i.e., most fragmented) encountered thus far.  The 10 worst
X * offenders are determined based on their absolute number of disk seeks
X * required to read the entire file.  Such an absolute test (viz a viz a
X * relative percentage test) ensures that very small, but fragmented, files
X * will not clutter the output.
X */
Xvoid log_stats (data)
Xstruct file_data *data;				/* file statistics to be
X						 * logged */
X{
X	int i, j;				/* loop counters */
X
X
X	/*
X	 * update global statistics
X	 */
X	blocks += data->total_blocks;
X	potential_seeks += data->potential_seeks;
X	seeks += data->seeks;
X	data->fragm = data->potential_seeks ? (float)data->seeks/(float)data->potential_seeks : 0.0;
X
X	/*
X	 * update 10 worst offender array
X	 */
X	for (i = 0; i < 10; i++){
X		if (data->seeks > file_log[i].seeks){
X			for (j = 9; j > i; j--){
X				file_log[j] = file_log[j-1];
X				}
X			file_log[i] = *data;
X			break;
X			}
X		}
X	}
X
X/*
X * scan : scan through each i-node of a file system, compiling statistics
X * regarding fragmentation and indirection.
X */
Xvoid scan (){
X	int	i, j;				/* loop counters */
X	struct dinode i_node[I_BLOCK_FACTOR];	/* holds a block of inodes */
X	struct file_data data;			/* per-inode statistics */
X
X	for (i = 1; i < num_inodes; i+=I_BLOCK_FACTOR){
X
X		/*
X		 * for efficiency, read a block of i-nodes at a time
X		 */
X		get_inodes (i, i_node, I_BLOCK_FACTOR);
X
X		/*
X		 * scan through each i-node that was read in
X		 */
X		for (j = 0; i+j < num_inodes && j < I_BLOCK_FACTOR; j++){
X			if (debug){
X				printf ("inode %d\r", i+j);
X				}
X			if (i+j <= 1)continue;	/* don't scan i-node 1 */
X			if (i_node[j].di_mode != 0){ /* unused i-node ? */
X				init_stats (&data, i+j);
X				check_file (&i_node[j], i+j, &data); /* scan blocks in file */
X				log_stats (&data);
X				}
X			else {
X				free_inodes++;
X				}
X			}
X		}
X	}
X
X/*
X * print_report : calculates percentages and prints a summary report of
X * file system statistics
X */
Xvoid print_report (){
X	long	num_files;			/* number of inodes used
X						 * in file system */
X	int	i;				/* loop counter */
X	
X	float	fragm,				/* percent fragmentation */
X		ind_p,				/* percent indirections */
X		dind_p,				/* percent double indirects */
X		tind_p,				/* percent triple indirects */
X		bused_p,			/* percent data blocks used */
X		iused_p,			/* percent inodes used */
X		bdir_p;				/* percent of directories
X						 * that have more than 10
X						 * blocks */
X
X	/*
X	 * calculate percentages for report
X	 */
X	fragm = potential_seeks ? (float)seeks/(float)potential_seeks : 0.0;
X	bused_p = (float)blocks/(float)(blocks+super.s_tfree);
X	num_files = num_inodes - free_inodes;
X	iused_p = (float)num_files/(float)num_inodes;
X	ind_p = num_files ? (float)indirects/(float)num_files : 0.0;
X	dind_p = num_files ? (float)double_indirects/(float)num_files : 0.0;
X	tind_p = num_files ? (float)triple_indirects/(float)num_files : 0.0;
X	bdir_p = num_directories ? (float)big_directories/(float)num_directories : 0.0;
X
X	/*
X	 * print out report
X	 */
X	printf ("\n\nFile system name = \"%.6s\", Volume name = \"%.6s\"\n",
X		super.s_fname, super.s_fpack);
X	printf ("File system logical block size = %d bytes\n", 512 * fs_type);
X	printf ("Volume Size = %ld blocks (%ld bytes)\n",
X		super.s_fsize, super.s_fsize * 512L * fs_type);
X	printf ("\t%u blocks reserved for super block and inodes\n", super.s_isize);
X	printf ("\t%lu blocks reserved for data\n", 
X		super.s_fsize - super.s_isize);
X	printf ("Total inodes = %d\n", num_inodes);
X	printf ("%.2f%% inodes used (%ld used, %ld free)\n", 
X		iused_p*100, num_inodes - free_inodes, free_inodes);
X	printf ("%.2f%% data blocks used (%ld used, %ld free)\n",
X		bused_p*100, blocks, super.s_tfree);
X	printf ("%d directories\n", num_directories);
X	printf ("%d multiply-linked files\n", linked_files);
X	printf ("\nFragmentation         = %.2f%%\n", fragm*100);
X/*	printf ("(%ld seeks of %ld potential)\n", seeks, potential_seeks); */
X	printf ("Indirects             = %ld (%.2f%%)\n",
X		indirects, ind_p*100);
X	printf ("Double indirects      = %ld (%.2f%%)\n",
X		double_indirects, dind_p*100);
X	printf ("Triple indirects      = %ld (%.2f%%)\n",
X		triple_indirects, tind_p*100);
X	printf ("Oversized directories = %d (%.2f%%)\n",
X		big_directories, bdir_p*100);
X	printf ("10 worst offenders:\n");
X	printf ("   i-node   Size  Fragments    %%\ti-node   Size  Fragments    %%\n");
X	for (i = 0; i < 5; i++){
X		if (file_log[i].inode != 0){
X			printf ("   %6d %6ld   %6ld   %6.2f%%",
X				file_log[i].inode,
X				file_log[i].data_blocks,
X				file_log[i].seeks+1,
X				file_log[i].fragm*100);
X			if (file_log[i+5].inode != 0){
X				printf ("\t%6d %6ld   %6ld   %6.2f%%",
X					file_log[i+5].inode,
X					file_log[i+5].data_blocks,
X					file_log[i+5].seeks+1,
X					file_log[i+5].fragm*100);
X				}
X			printf ("\n");
X			}
X		else break;
X		}
X	}
X
X/*
X * anal_file : analyzes a single file for fragmentation.
X */
Xvoid anal_file (ino, fname)
Xint ino;					/* i-node number */
Xchar *fname;					/* filename of this inode */
X{
X	struct file_data data;			/* current file statistics */
X
X	struct dinode i_node;			/* current inode data */
X
X	get_inodes (ino, &i_node, 1);
X	init_stats(&data, ino);
X	check_file (&i_node, ino, &data);
X	log_stats (&data);
X	printf ("   %-14s\t%6d    %6ld   %6ld   %6.2f%%\n",
X		fname, data.inode, data.seeks+1, 
X                       data.total_blocks, data.fragm*100);
X	}
X
Xmain (argc, argv)
Xint argc;
Xchar *argv[];
X{
X	int next_param;
X	struct stat f_stat;
X
X	/*
X	 * perform various initialization functions
X	 */
X	next_param = init (argc, argv);
X
X	if (next_param == argc){
X		/*
X		 * no individual files to check, scan entire file system
X		 */
X		printf ("Analyzing file system %s...\n", special);
X
X		/*
X		 * scan through all i-nodes in the file system
X		 */
X		scan();
X
X		/*
X		 * print out statistics summary
X		 */
X		print_report();
X		}
X	else {
X		/*
X		 * scan individual files instead of entire file system
X		 */
X		printf ("        Name      \ti-node    Fragments  Size      %%\n");
X		for (; next_param < argc; next_param++){
X			if (stat (argv[next_param], &f_stat) != 0){
X				error (errno, "error opening \"%s\"\n",
X				       argv[next_param]);
X				}
X			else {
X				if (!IS_SPECIAL (f_stat.st_mode)){
X					anal_file (f_stat.st_ino, argv[next_param]);
X					}
X				}
X			}
X		}
X	}
END_OF_fsanalyze.c
if test 25225 -ne `wc -c <fsanalyze.c`; then
    echo shar: \"fsanalyze.c\" unpacked with wrong size!
fi
# end of overwriting check
fi
echo shar: End of archive 1 \(of 1\).
cp /dev/null ark1isdone
MISSING=""
for I in 1 ; do
    if test ! -f ark${I}isdone ; then
	MISSING="${MISSING} ${I}"
    fi
done
if test "${MISSING}" = "" ; then
    echo You have unpacked all 1 archives.
    rm -f ark[1-9]isdone
else
    echo You still need to unpack the following archives:
    echo "        " ${MISSING}
fi
##  End of shell archive.
exit 0
--
Mike Young - Software Development Technologies, Inc., Sudbury MA 01776
UUCP     : {decvax,harvard,linus,mit-eddie}!necntc!necis!mrst!sdti!mjy
Internet : mjy%sdti.uucp@harvard.harvard.edu      Tel: +1 617 443 5779
"What would you do with a brain if you had one?" -- Dorothy, Wiz of Oz