v08i057: Directory hiearchy scanner

sources-request at mirror.UUCP sources-request at mirror.UUCP
Fri Feb 13 03:43:58 AEST 1987


Submitted by: Alan Silverstein <seismo!hplabs!hpfcla!hpfcdt!ajs>
Mod.sources: Volume 8, Issue 57
Archive-name: hier

hier(1) is yet another way to view a directory hierarchy.  It's
analogous to ls -R, but presents the data in a new fashion especially
useful for novice users or display on a wall.  Now, with the plethora of
similar tools available, what's special about this one?

- I wrote it after four *years* of consideration and using similar tools.
- I interacted with an "end user" who knew what he wanted.
- The code is carefully crafted, commented, and tested.  (But not yet
  widely ported, I fear.)

So give it a try, and see if you like it.

Also note!  This package includes a "sorted ftw()", which is a major
and non-trivial library routine you'll love if you are familiar with
ftw(3) (file tree walk), but wish you could get sorted results.

Alan Silverstein, Hewlett-Packard Systems Software Operation, Fort Collins,
Colorado; {ihnp4 | hplabs}!hpfcla!ajs; 303-229-3053; (lat-long on request :-)

[  I ported SFTW to BSD.  This included writing (most of?) <ftw.h>
   and a couple of real minor tweaks.  I started on HIER, but gave up
   at the first bug.  A public-domain manpage for sftw would be real
   nice.  -r$ ]

#! /bin/sh
# This is a shell archive.  Remove anything before this line,
# then unpack it by saving it in a file and typing "sh file".
# If all goes well, you will see the message "End of shell archive."
# Contents:  Makefile Makefile.bsd ftw.h hier.1 hier.c sftw.c
PATH=/bin:/usr/bin:/usr/ucb; export PATH
echo shar: extracting "'Makefile'" '(324 characters)'
if test -f 'Makefile' ; then 
  echo shar: will not over-write existing file "'Makefile'"
else
sed 's/^X//' >Makefile <<'@//E*O*F Makefile//'
X# Makefile for hier(1), needed to link with sftw().
X
Xhier: hier.c sftw.o
X	cc -v -O -s -o hier hier.c sftw.o
X
Xsftw.o: sftw.c
X	cc -v -O -c sftw.c
X
Xdebug: hier.c sftw.c
X	cc -v -g -o hier hier.c sftw.c
X	rm -f hier.o sftw.o	# since they're useless.
X
Xtest: sftw.c
X	cc -v -O -o sftw sftw.c -DTEST
X
Xclean:
X	rm -f *.o hier sftw core
@//E*O*F Makefile//
if test 324 -ne "`wc -c <'Makefile'`"; then
    echo shar: error transmitting "'Makefile'" '(should have been 324 characters)'
fi
fi # end of overwriting check
echo shar: extracting "'Makefile.bsd'" '(298 characters)'
if test -f 'Makefile.bsd' ; then 
  echo shar: will not over-write existing file "'Makefile.bsd'"
else
sed 's/^X//' >Makefile.bsd <<'@//E*O*F Makefile.bsd//'
X# Makefile for hier(1), needed to link with sftw().
X# This is for BSD sites.  mirror!rs
X
XCFLAGS = -O -DBSD -I. -Dstrrchr=rindex
XGETOPT=-lgetopt
X
Xhier: hier.c sftw.o
X	cc $(CFLAGS) -s -o hier hier.c sftw.o $(GETOPT)
X
Xtest: sftw.c
X	cc $(CFLAGS) -DTEST -o sftw sftw.c
X
Xclean:
X	rm -f *.o hier sftw core
@//E*O*F Makefile.bsd//
if test 298 -ne "`wc -c <'Makefile.bsd'`"; then
    echo shar: error transmitting "'Makefile.bsd'" '(should have been 298 characters)'
fi
fi # end of overwriting check
echo shar: extracting "'ftw.h'" '(576 characters)'
if test -f 'ftw.h' ; then 
  echo shar: will not over-write existing file "'ftw.h'"
else
sed 's/^X//' >ftw.h <<'@//E*O*F ftw.h//'
X/*
X**  This is a version of <ftw.h> for sites that don't have it.  I just
X**  picked four values at random for the #define's that sftw needed; I
X**  doubt that this is binary-compatible with AT&T's <ftw.h>, and this
X**  may well be missing something.  Oh, well, it's public-domain...
X**	-Rich $alz, mirror!rs
X*/
X
X
X/*
X**  These values are passed on to the user's function, as the third
X**  parameter.
X*/
X#define	FTW_F		1	/* A file				*/
X#define	FTW_D		2	/* A directory				*/
X#define	FTW_DNR		3	/* A directory that couldn't be read	*/
X#define	FTW_NS		4	/* A stat() failure			*/
@//E*O*F ftw.h//
if test 576 -ne "`wc -c <'ftw.h'`"; then
    echo shar: error transmitting "'ftw.h'" '(should have been 576 characters)'
fi
fi # end of overwriting check
echo shar: extracting "'hier.1'" '(1732 characters)'
if test -f 'hier.1' ; then 
  echo shar: will not over-write existing file "'hier.1'"
else
sed 's/^X//' >hier.1 <<'@//E*O*F hier.1//'
X.TH HIER 1 "Unsupported Utility"
X.SH NAME
Xhier \- show filesystem hierarchy
X.SH SYNOPSIS
X.B hier
X[
X.B \-adp
X] [
X.BI \-c \0columns
X] [
X.BI \-i \0indent
X]
X.br
X[
X.I directories...
X]
X.ad b
X.SH DESCRIPTION
XThis command shows you a filesystem hierarchy in a useful, indented way.
XAt each level files are sorted in two groups:
Xnon-directory files,
Xthen directories (recursing into each one).
XIt examines the named
X.IR directories ,
Xor by default the present working directory.
X.PP
XOptions are:
X.TP
X.B \-a
XAll: include directories and files whose names start with ".".
X.TP
X.B \-d
XShow directories only; skip other types of files.
X.TP
X.B \-p
XPrint filenames packed onto lines, not aligned in columns.
X.TP
X.B \-c
XSet width of display for showing multiple filenames on a line
X(or use the COLUMNS environment variable).
XThe default is 80 columns.
X.TP
X.B \-i
XSet indentation (number of blanks) per hierarchy level.
XThe default is 4 spaces per level.
X.SH EXAMPLES
X.TP
Xhier
X.br
XShow all non-"." files, recursively,
Xin and under the current directory.
X.TP
Xhier -apc 40 /etc
XShow all directories and files,
Xincluding any whose filenames start with ".",
Xin a format 40 columns wide,
Xand with filenames packed into lines,
Xunder directory "/etc".
X.SH SEE ALSO
Xls(1), sftw(3) (does not exist (yet))
X.SH DIAGNOSTICS
XIf a file is not stat-able,
Xor a directory is not readable,
Xthe filename is printed on a line to itself,
Xlike a directory (sorted with directory names),
Xwith an appropriate message following.
X.SH BUGS
XUnlike
X.IR ls (1),
Xit sorts files across lines rather than down columns.
XFixing this would be non-trivial.
X.PP
XAlso, due to the behavior of
X.IR sftw (3)
X(like
X.IR ftw (3)),
Xit never lists "." and ".." files, even with the
X.B \-a
Xoption.
@//E*O*F hier.1//
if test 1732 -ne "`wc -c <'hier.1'`"; then
    echo shar: error transmitting "'hier.1'" '(should have been 1732 characters)'
fi
fi # end of overwriting check
echo shar: extracting "'hier.c'" '(8409 characters)'
if test -f 'hier.c' ; then 
  echo shar: will not over-write existing file "'hier.c'"
else
sed 's/^X//' >hier.c <<'@//E*O*F hier.c//'
X/*
X * Show file system hierarchy.
X *
X * Usage: see below.
X *
X * Unlike ls(1), it sorts files across lines rather than down columns.
X * Fixing this would be non-trivial, involving saving filenames until it
X * was time to dump them.
X *
X * Also, due to the behavior of sftw() (like ftw()), it never lists "." and
X * ".." files.
X *
X * Warning:  If you use ftw() instead of sftw(), -a option will stop working.
X *
X * Warning:  If you use ftw() instead of sftw(), a bug will appear.  This is
X * because two calls in a row from ftw() to OneFile() may be made for ordinary
X * files, where the second is in a directory a level above the first one.
X * OneFile() would have to check to see if each ordinary file's path is
X * different than the previous one's, indicating a change of directory level.
X */
X
X#include <sys/types.h>
X#include <sys/stat.h>
X#include <stdio.h>
X#include <ftw.h>
X#ifdef	BSD
X#include <sys/dir.h>			/* for DIRSIZ */
X#include <strings.h>
X#else
X#include <sys/ndir.h>			/* for DIRSIZ */
X#include <string.h>
X#endif	/* BSD */
X
X
X/*********************************************************************
X * MISCELLANEOUS GLOBAL VALUES:
X */
X
X#define	PROC				/* null; easy to find procs */
X#define	FALSE	0
X#define	TRUE	1
X#define	CHNULL	('\0')
X#define	CPNULL	((char *) NULL)
X#define	REG	register
X
Xchar *usage[] = {
X    "usage: %s [-adp] [-c columns] [-i indent] [directories...]",
X    "-a include directories and files whose names start with \".\"",
X    "-d show directories only",
X    "-p print filenames packed onto lines, not aligned",
X    "-c set width of display (or use COLUMNS env variable; default = 80)",
X    "-i set indentation per level; default = 4",
X    "Does current directory by default.",
X    CPNULL,
X};
X
Xchar	*myname;			/* how program was invoked	*/
Xint	aflag	= FALSE;		/* -a (all files) option	*/
Xint	dflag	= FALSE;		/* -d (directories) option	*/
Xint	pflag	= FALSE;		/* -p (packed filenames) option	*/
Xint	columns	= 0;			/* from -c option or env	*/
Xint	indent	= 0;			/* from -i option or default	*/
X
X#define	COLUMNS	80			/* width of display		*/
X#define	INDENT	 4			/* per directory level		*/
X
Xint	startlen;			/* of current arg (filename)	*/
Xint	nextcol = 0;			/* column in output line	*/
X
X
X/************************************************************************
X * M A I N
X *
X * Initialize, then call sftw() for each given filename after clearing
X * global startlen to indicate a new starting file.  When done, if global
X * nextcol != 0 (in the middle of an output line), finish the last line.
X */
X
XPROC main (argc, argv)
X	int	argc;
X	char	**argv;
X{
Xextern	int	optind;			/* from getopt()	*/
Xextern	char	*optarg;		/* from getopt()	*/
XREG	int	option;			/* option "letter"	*/
X	char	*argdef = ".";		/* default argument	*/
X	char	*colstr;		/* from environment	*/
X
X	char	*getenv();
X	int	OneFile();
X
X/* #define DEPTH (_NFILE - 5)		/* for ftw(), but not sftw() */
X
X/*
X * PARSE ARGUMENTS:
X */
X
X	myname = *argv;
X
X	while ((option = getopt (argc, argv, "adpc:i:")) != EOF)
X	{
X	    switch (option)
X	    {
X		case 'a':	aflag	= TRUE;			break;
X		case 'd':	dflag	= TRUE;			break;
X		case 'p':	pflag	= TRUE;			break;
X		case 'c':	columns	= atoi (optarg);	break;
X		case 'i':	indent	= atoi (optarg);	break;
X		default:	Usage();
X	    }
X	}
X
X	if (dflag && pflag)
X	    Error ("-d and -p don't make sense together");
X
X/*
X * FINISH INITIALIZING:
X */
X
X	if ((columns == 0)				/* no value given */
X	 && (((colstr = getenv ("COLUMNS")) == CPNULL)	/* undefined	  */
X	  || ((columns = atoi (colstr)) == 0)))	  /* defined null or zero */
X	{
X	    columns = COLUMNS;		/* use default */
X	}
X
X	if (indent == 0)		/* no value given */
X	    indent = INDENT;		/* use default	  */
X
X	argc -= optind;			/* skip options	*/
X	argv += optind;
X
X	if (argc == 0)			/* no filenames given */
X	{
X	    argc = 1;
X	    argv = & argdef;		/* use default */
X	}
X
X/*
X * WALK EACH FILE TREE:
X */
X
X	while (argc-- > 0)
X	{
X	    startlen = 0;
X
X	    if (sftw (*argv, OneFile, aflag))
X		Error ("file tree walk failed for file \"%s\"", *argv);
X
X	    argv++;
X	}
X
X	if (nextcol)			/* last line not finished */
X	    putchar ('\n');
X
X	exit (0);
X
X} /* main */
X
X
X/************************************************************************
X * O N E   F I L E
X *
X * Called from sftw() to handle (print) one file, given a filename (starting
X * file plus sub-file part) and ftw() file type.  Always returns zero (all
X * is well, keep going).
X *
X * It's messy because of the need to print multiple non-directory basenames
X * on one line.  Uses global startlen to save time figuring depth beyond
X * starting file.  If currently zero, this is the starting file; print the
X * fullname, on a line alone, with no indent.
X *
X * Use globals startlen, indent, columns, and nextcol.
X */
X
XPROC int OneFile (filename, statp, type)
X	char	*filename;	/* name		*/
X	struct	stat *statp;	/* info, unused	*/
X	int	type;		/* ftw() type	*/
X{
XREG	char	*basename;	/* part of filename */
X
X/*
X * PRINTING FORMATS (matching ftw() types):
X */
X
Xstatic	char	*FMT_D   = "%s/\n";
Xstatic	char	*FMT_DNR = "%s/ (not readable)\n";
Xstatic	char	*FMT_NS  = "%s (not stat'able)\n";
Xstatic	char	*FMT_F1	 = "%s\n";		/* for starting file */
Xstatic	char	*FMT_F2	 = "%-*s";		/* for sub-file	     */
X
X#ifdef	BSD
X#define	FILEWIDTH MAXNAMLEN			/* for FMT_F2 */
X#else
X#define	FILEWIDTH (DIRSIZ + 1)			/* for FMT_F2 */
X#endif	/* BSD */
XREG	int	filewidth = FILEWIDTH;		/* if ! pflag */
X
X#define	NEWLINE	 { putchar ('\n'); nextcol = 0; }  /* for speed and clarity */
X
X/*
X * OPTIONALLY IGNORE NON-DIRECTORY (even if named as an input argument):
X */
X
X	if (dflag && (type == FTW_F))
X	    return (0);
X
X/*
X * HANDLE STARTING FILE:
X */
X
X	if (startlen == 0)
X	{
X	    startlen = strlen (filename);	/* set for later */
X
X	    if (nextcol)		/* end previous line */
X		NEWLINE;		/* sets nextcol == 0 */
X
X	    printf ((type == FTW_D)   ? FMT_D	:
X		    (type == FTW_DNR) ? FMT_DNR	:
X		    (type == FTW_NS)  ? FMT_NS	: FMT_F1, filename);
X
X	    return (0);
X	}
X
X/*
X * SET BASENAME FOR ALL OTHER TYPES:
X */
X
X	basename = ((basename = strrchr (filename, '/')) == CPNULL) ?
X		   filename : (basename + 1);	/* past "/" if any */
X
X/*
X * HANDLE NON-DIRECTORY SUB-FILE (print multiple per line):
X */
X
X	if (type == FTW_F)
X	{
X	    if (pflag)				/* else use preset value */
X		filewidth = strlen (basename) + 1;
X
X	    if (nextcol	&& (nextcol + filewidth >= columns))	/* overflow */
X		NEWLINE;			/* sets nextcol == 0 */
X
X	    if (nextcol == 0)		/* start new line with indent */
X		nextcol = PrintIndent (filename);
X
X	    printf (FMT_F2, filewidth, basename);
X	    nextcol += filewidth;
X	    return (0);
X	}
X
X/*
X * HANDLE DIRECTORY OR OTHER SUB-FILE (print on line by itself):
X */
X
X	if (nextcol)			/* end previous line */
X	    NEWLINE;			/* sets nextcol == 0 */
X
X	PrintIndent (filename);
X
X	printf ((type == FTW_D)   ? FMT_D   :
X		(type == FTW_DNR) ? FMT_DNR : FMT_NS, basename);
X
X	return (0);
X
X} /* OneFile */
X
X
X/************************************************************************
X * P R I N T   I N D E N T
X *
X * Given a filename and globals startlen and indent, print the total
X * indentation needed before the name, which is indent times the number of
X * slashes past startlen (which should be >= 1).  Return the indent value.
X */
X
XPROC int PrintIndent (filename)
XREG	char	*filename;
X{
XREG	int	depth = 0;
XREG	int	totind;
X	int	retval;
X
X	filename += startlen;		/* start of sub-part */
X
X	while (*filename != CHNULL)
X	    if (*filename++ == '/')
X		depth++;
X
X	retval = totind = indent * depth;
X
X	while (totind-- > 0)
X	    putchar (' ');
X
X	return (retval);
X
X} /* PrintIndent */
X
X
X/************************************************************************
X * U S A G E
X *
X * Print usage messages (char *usage[]) to stderr and exit nonzero.
X * Each message is followed by a newline.
X */
X
XPROC Usage()
X{
XREG	int	which = 0;		/* current line */
X
X	while (usage [which] != CPNULL)
X	{
X	    fprintf (stderr, usage [which++], myname);
X	    putc ('\n', stderr);
X	}
X
X	exit (1);
X
X} /* Usage */
X
X
X/************************************************************************
X * E R R O R
X *
X * Print an error message to stderr and exit nonzero.  Message is preceded
X * by "<myname>: " using global char *myname, and followed by a newline.
X */
X
X/* VARARGS */
XPROC Error (message, arg1, arg2, arg3, arg4)
X	char	*message;
X	long	arg1, arg2, arg3, arg4;
X{
X	fprintf (stderr, "%s: ", myname);
X	fprintf (stderr, message, arg1, arg2, arg3, arg4);
X	putc ('\n', stderr);
X
X	exit (1);
X
X} /* Error */
@//E*O*F hier.c//
if test 8409 -ne "`wc -c <'hier.c'`"; then
    echo shar: error transmitting "'hier.c'" '(should have been 8409 characters)'
fi
fi # end of overwriting check
echo shar: extracting "'sftw.c'" '(14313 characters)'
if test -f 'sftw.c' ; then 
  echo shar: will not over-write existing file "'sftw.c'"
else
sed 's/^X//' >sftw.c <<'@//E*O*F sftw.c//'
X/*
X * Sorted file tree walk (library routine).
X *
X * Identical (in theory) to ftw(3), except:
X *
X * - Calls user's fn() with the files sorted alphabetically (per strcmp(3))
X *   in two groups:  All non-directories first, followed by directories (with
X *   the descendents of each directory after the directory).  Non-stat'able
X *   files and non-readable directories are included in the second group.
X *
X * - Doesn't keep one file open for each level of recursion, so it doesn't
X *   need a depth argument (which actually affects file opens/closes, NOT
X *   maximum search depth).
X *
X * - Uses a lot more malloc space.
X *
X * - Supports an additional argument which tells it to include all files
X *   and directories, including those whose names start with "." (except that
X *   the given filename is always included, regardless of the flag, like
X *   ls(1)).  The caller could implement this, but not very efficiently.
X *
X * Like ftw(), it ignores "." and ".." files, even with the all flag.
X *
X * For convenience, form of call is:
X *
X *	#include <ftw.h>
X *
X *	int sftw (path, fn, allfiles)
X *		char	*path;
X *		int	(*fn)();
X *		int	allfiles;
X *
X * Form of fn() is:
X *
X *	int fn (name, statp, type)
X *		char	*name;
X *		struct	stat *statp;
X *		int	type;
X *
X * See ftw(3) for more information.
X *
X * Compile with -DTEST to get a runnable test version that walks from "."
X * and tells types, permissions, and filenames passed to fn().
X */
X
X#include <sys/types.h>
X#include <sys/stat.h>
X#include <ftw.h>
X#ifdef	BSD
X#include <sys/dir.h>
X#else
X#include <ndir.h>
X#endif	/* BSD */
X
Xstatic char *malloc(), *strcpy();
Xstatic void free();
X
X
X/*********************************************************************
X * MISCELLANEOUS GLOBAL VALUES:
X */
X
X#define	PROC				/* null; easy to find procs */
X#define	FALSE	0
X#define	TRUE	1
X#define	CHNULL	('\0')
X#define	CPNULL	((char *) NULL)
X#define	REG	register
X
X/* built-up filename for passing to the user program; hope it's big enough */
Xstatic	char filename [1000];
X
Xstatic	unsigned short euid, egid;	/* only get them once */
X
X
X
X/************************************************************************
X * FILE DATA STRUCTURE:
X *
X * A contiguous array of pointers is used for sorting, built after knowing
X * how many directory entries there are to sort.  Each entry points to a
X * struct filedata which holds information for one directory entry.
X */
X
Xtypedef	struct filedata *fdpt;
Xtypedef	struct filedata **fdppt;
X
Xstruct filedata {
X	char	*name;		/* in malloc'd space	*/
X	int	type;		/* see ftw.h		*/
X	struct	stat statbuf;	/* from stat(2)		*/
X};
X
X
X/************************************************************************
X * FILE AND STRING DATA BLOCKS:
X *
X * Since a directory may grow arbitrarily as it's read, there's no way to
X * know in advance how big it is.  And it's necessary to return all malloc'd
X * memory.  To make this possible, and to save malloc space and time, directory
X * entry data and filenames are stored in buffers allocated a chunk a time.
X */
X
X#define	DBENTRIES	20	/* file entries per datablock */
X#define	STRINGENTRIES 1008	/* chars per string buffer    */
X
Xtypedef	struct datablock *dbpt;
Xtypedef	struct datablock **dbppt;
X
Xstruct datablock {
X	dbpt	next;				/* next block if any */
X	struct	filedata fd [DBENTRIES];	/* the data itself   */
X};
X
X#define	DBNULL  ((dbpt) NULL)
X#define	DBSIZE  (sizeof (struct datablock))
X
X
Xtypedef struct stringblock *sbpt;
Xtypedef struct stringblock **sbppt;
X
Xstruct stringblock {
X	sbpt	next;				/* next block if any */
X	char	buf [STRINGENTRIES];		/* the data itself   */
X};
X
X#define	SBNULL  ((sbpt) NULL)
X#define	SBSIZE  (sizeof (struct stringblock))
X
X
X/************************************************************************
X * S F T W
X *
X * Handle the filename given by the user.  Since sftw() must stat() each
X * file before sorting, the first (top level) file must be handled specially,
X * not as part of re-entrant code.  (Think about it...)
X */
X
XPROC int sftw (path, UserFunc, allfiles)
X	char	*path;
X	int	(*UserFunc)();
X	int	allfiles;
X{
X	struct	stat statbuf;		/* from first file	*/
X	int	type;			/* of first file	*/
X	int	retval;			/* return by UserFunc()	*/
X	unsigned short geteuid(), getegid();
X
X	euid = geteuid();	/* initialize values */
X	egid = getegid();
X
X/*
X * HANDLE INITIAL FILE:
X */
X
X	type = GetType (path, & statbuf);
X
X	if (retval = UserFunc (path, & statbuf, type))	/* it's unhappy */
X	    return (retval);
X
X	if (type != FTW_D)			/* we're done */
X	    return (0);
X
X/*
X * WORK ON A READABLE DIRECTORY:
X */
X
X	strcpy (filename, path);		/* now we can append to it */
X	strcat (filename, "/");			/* prepare for additions   */
X
X	return (DoDirectory (UserFunc, allfiles));
X
X} /* sftw */
X
X
X/************************************************************************
X * D O   D I R E C T O R Y
X *
X * Given UserFunc(), all files flag, and global filename (directory path) where
X * to start and on which to build complete pathnames, read the directory, sort
X * filenames, and call UserFunc() for each file in the directory.  This routine
X * calls itself to recurse, after each directory name is passed to UserFunc().
X * Because it reads and saves a directory's contents in order to sort them, it
X * does not keep any files open while recursing, just lots of memory.
X *
X * Free all memory from this level before returning, even in case of error.
X * Return -1 in case of error, or the value from UserFunc() if non-zero.
X */
X
XPROC static int DoDirectory (UserFunc, allfiles)
X	int	(*UserFunc)();
X	int	allfiles;		/* include ".*" files?	  */
X{
X	dbpt	dbhead = DBNULL;	/* first datablock ptr	  */
X	sbpt	sbhead = SBNULL;	/* first stringblock ptr  */
X
X	fdppt	fdphead;		/* filedata list to sort  */
XREG	fdppt	fdpcurr;		/* current list pointer	  */
XREG	fdpt	fdcurr;			/* current entry pointer  */
XREG	int	files;			/* number in directory	  */
X
X	int	retval;			/* copy of return value	  */
X
X/* pointer into filename where to append basenames */
XREG	char	*basename = filename + strlen (filename);
X
X	int	FDCmp();
X	void	qsort();
X
X#define	RETURN(value) { FreeBlocks (dbhead, sbhead); return (value); }
X
X/*
X * READ DIRECTORY:
X */
X
X	if ((files = ReadDirectory (& dbhead, & sbhead, allfiles)) < 0)
X	    RETURN (-1);
X
X/*
X * BUILD AND SORT POINTERS TO FILES:
X *
X * Get a big chunk of contiguous memory for the pointers, then set them up.
X * Afterwards, filedata entries will be accessed via the pointers.
X */
X
X	if ((fdphead = (fdppt) malloc (files * sizeof (fdpt))) == (fdppt) NULL)
X	    RETURN (-1);
X
X#undef	RETURN
X#define	RETURN(value) { FreeBlocks (dbhead, sbhead); \
X			free ((char *) fdphead); return (value); }
X
X	SetFDList (fdphead, fdphead + files, dbhead);
X	qsort ((char *) fdphead, (unsigned) files, sizeof (fdpt), FDCmp);
X
X/*
X * TRAVERSE FILES USING SORTED POINTERS:
X *
X * Append each file's basename to the current path in global filename,
X * overlaying whatever basename was there before, and pass it to UserFunc().
X */
X
X	fdpcurr = fdphead;
X
X	while (files-- > 0)
X	{
X	    strcpy (basename, (fdcurr = (*fdpcurr++)) -> name);
X
X	    if (retval = UserFunc (filename, & (fdcurr -> statbuf),
X				   fdcurr -> type))  /* it's unhappy */
X	    {
X		RETURN (retval);
X	    }
X
X/*
X * RECURSE FOR A DIRECTORY:
X */
X
X	    if ((fdcurr -> type) == FTW_D)
X	    {
X		strcat (basename, "/");		/* for next level */
X
X		if (retval = DoDirectory (UserFunc, allfiles))
X		    RETURN (retval);
X	    }
X	}
X
X	RETURN (0);
X
X} /* DoDirectory */
X
X
X/************************************************************************
X * R E A D   D I R E C T O R Y
X *
X * Given pointers to datablock and stringblock chain head pointers, all files
X * flag, and global filename (name of a readable directory) on which to build
X * complete pathnames, open, read, and close a directory, saving name, stat,
X * and type information on each file in a chain of datablocks and stringblocks,
X * and setting the chain head pointers.  Return the number of filenames read
X * and saved (>= 0), or -1 in case of error, but always close the directory.
X */
X
XPROC static int ReadDirectory (dbheadp, sbheadp, allfiles)
X	dbppt	dbheadp;		/* start of chain	*/
X	sbppt	sbheadp;		/* start of chain	*/
X	int	allfiles;		/* include ".*" files?	*/
X{
XREG	DIR	*dirp;			/* for reading directory  */
XREG	struct	direct *entp;		/* directory entry	  */
XREG	char	*name;			/* fast copy of filename  */
X
XREG	dbpt	dbcurr;			/* current datablock ptr  */
X	dbppt	dbnextp = dbheadp;	/* next datablock ptr ptr */
XREG	int	dbentry = DBENTRIES;	/* current entry in block */
X
XREG	fdpt	fdcurr;			/* current filedata entry */
X
XREG	sbpt	sbcurr;			/* current stringblock ptr  */
X	sbppt	sbnextp = sbheadp;	/* next stringblock ptr ptr */
XREG	char	*end   = "";		/* last + 1 of block	    */
XREG	char	*start = end;		/* next free place	    */
X
X/* pointer into filename where to append basenames */
XREG	char	*basename = filename + strlen (filename);
XREG	int	files	  = 0;		/* files read and saved	  */
X
X#undef	RETURN
X#define	RETURN(value) { closedir (dirp); return (value); }
X
X/*
X * OPEN AND READ DIRECTORY:
X */
X
X	if ((dirp = opendir (filename)) == (DIR *) NULL)
X	    return (-1);		/* hope errno is set */
X
X	/* now be sure to use the RETURN() macro */
X
X	while ((entp = readdir (dirp)) != (struct direct *) NULL)
X	{
X
X/*
X * OPTIONALLY SKIP ".*" FILES:
X *
X * Always skip "." and ".." files, like ftw().
X */
X
X	    if ((* (name = entp -> d_name) == '.')	/* fast check */
X	     && ((! allfiles)
X	      ||  (name [1] == CHNULL)
X	      || ((name [1] == '.') && (name [2] == CHNULL))))
X	    {
X		continue;
X	    }
X
X	    files++;
X
X/*
X * GET A NEW DATABLOCK; APPEND TO CHAIN:
X */
X
X	    if (dbentry >= DBENTRIES)		/* block is full */
X	    {
X		if ((dbcurr = *dbnextp = (dbpt) malloc (DBSIZE)) == DBNULL)
X		    RETURN (-1);
X
X		* (dbnextp = & (dbcurr -> next)) = DBNULL;
X		dbentry = 0;
X	    }
X
X/*
X * GET A NEW STRINGBLOCK; APPEND TO CHAIN:
X *
X * Yes, we may abandon some unused space in the previous block...  Hope that
X * STRINGENTRIES is much larger than the average directory entry name size.
X */
X
X	    if ((entp -> d_namlen) + 1 > (end - start))
X	    {
X		if ((sbcurr = *sbnextp = (sbpt) malloc (SBSIZE)) == SBNULL)
X		    RETURN (-1);
X
X		* (sbnextp = & (sbcurr -> next)) = SBNULL;
X		end = (start = (sbcurr -> buf)) + STRINGENTRIES;
X	    }
X
X/*
X * SAVE INFORMATION ON ONE FILE:
X *
X * Append each file's basename to the current path in global filename,
X * overlaying whatever basename was there before, and pass it to GetType().
X */
X
X	    fdcurr = (dbcurr -> fd) + (dbentry++);	/* quick pointer */
X
X	    strcpy (((fdcurr -> name) = start), name);
X	    start += (entp -> d_namlen) + 1;
X
X	    strcpy (basename, name);
X	    (fdcurr -> type) = GetType (filename, & (fdcurr -> statbuf));
X
X	} /* while */
X
X	RETURN (files);
X
X} /* ReadDirectory */
X
X
X/************************************************************************
X * G E T   T Y P E
X *
X * Given a filename and a pointer to a stat structure, stat() the file into the
X * structure and return an appropriate ftw() type.  Since directories are not
X * opened for reading until much later, after sorting, determine readability
X * now using euid, egid, and st_mode.  Can't use access(2) because it checks
X * real ids, not effective (sigh).
X */
X
XPROC static int GetType (name, statp)
X	char	*name;
X	struct	stat *statp;
X{
X#define	UREAD (S_IREAD)	     /* read permission bits for user, group, other */
X#define	GREAD (S_IREAD >> 3)
X#define	OREAD (S_IREAD >> 6)
X
X	if (stat (name, statp) < 0)
X	    return (FTW_NS);			/* not stat'able */
X
X	if (((statp -> st_mode) & S_IFMT) != S_IFDIR)
X	    return (FTW_F);			/* not directory */
X
X	/* pick appropriate permission bit, then see if it's set */
X
X	return (
X	    ( ((statp -> st_uid) == euid) ? ((statp -> st_mode) & UREAD) :
X	      ((statp -> st_gid) == egid) ? ((statp -> st_mode) & GREAD) :
X					    ((statp -> st_mode) & OREAD) ) ?
X	    FTW_D : FTW_DNR);
X
X} /* GetType */
X
X
X/************************************************************************
X * S E T   F D   L I S T
X *
X * Given pointers to the current (head) and end + 1 of an array of
X * uninitialized struct filedata pointers, and a current (head) struct
X * datablock pointer, fill in the pointers in the array to point to the
X * filedata entries in the datablock chain.
X */
X
XPROC static SetFDList (fdpcurr, fdpend, dbcurr)
XREG	fdppt	fdpcurr;	/* start at head */
XREG	fdppt	fdpend;		/* last + 1	 */
XREG	dbpt	dbcurr;		/* start at head */
X{
XREG	int	dbentry;	/* current index */
X
X	while (TRUE)				/* until return */
X	{
X	    for (dbentry = 0; dbentry < DBENTRIES; dbentry++)	/* one block */
X	    {
X		if (fdpcurr >= fdpend)		/* no more */
X		    return;
X
X		*fdpcurr++ = (dbcurr -> fd) + dbentry;
X	    }
X
X	    dbcurr = dbcurr -> next;
X	}
X
X	/* never get here */
X
X} /* SetFDList */
X
X
X/************************************************************************
X * F D   C M P
X *
X * Given two pointers to pointers to filedata entries, compare the entries
X * and return -1, 0, or 1 for how they relate.  "Normal" files (FTW_F)
X * are always lower than other types, then names are compared with strcmp().
X */
X
XPROC static int FDCmp (fdpp1, fdpp2)
X	fdppt	fdpp1, fdpp2;
X{
XREG	int	type1 = (*fdpp1) -> type;
XREG	int	type2 = (*fdpp2) -> type;
X
X	return (((type1 == FTW_F) && (type2 != FTW_F)) ? -1 :
X		((type1 != FTW_F) && (type2 == FTW_F)) ?  1 :
X		strcmp ((*fdpp1) -> name, (*fdpp2) -> name));
X
X} /* FDCmp */
X
X
X/************************************************************************
X * F R E E   B L O C K S
X *
X * Given pointers to heads of datablock and stringblock chains, free the
X * malloc'd memory in the chains.
X */
X
XPROC static FreeBlocks (dbhead, sbhead)
XREG	dbpt	dbhead;
XREG	sbpt	sbhead;
X{
XREG	dbpt	dbtemp;
XREG	sbpt	sbtemp;
X
X	while (dbhead != DBNULL)
X	{
X	    dbtemp = dbhead -> next;
X	    free ((char *) dbhead);
X	    dbhead = dbtemp;
X	}
X
X	while (sbhead != SBNULL)
X	{
X	    sbtemp = sbhead -> next;
X	    free ((char *) sbhead);
X	    sbhead = sbtemp;
X	}
X
X} /* FreeBlocks */
X
X
X#ifdef TEST
X
X/************************************************************************
X * TEST HARNESS:
X */
X
XPROC int fn (name, statp, type)
X	char	*name;
X	struct	stat *statp;
X	int	type;
X{
X	printf ("%-3s %06o \"%s\"\n",
X		(type == FTW_F)   ? "F"   : (type == FTW_D)  ? "D"  :
X		(type == FTW_DNR) ? "DNR" : (type == FTW_NS) ? "NS" : "?",
X		statp -> st_mode, name);
X
X	return (0);
X}
X
XPROC main()
X{
X	printf ("sftw returns %d\n", sftw (".", fn));
X}
X
X#endif TEST
@//E*O*F sftw.c//
if test 14313 -ne "`wc -c <'sftw.c'`"; then
    echo shar: error transmitting "'sftw.c'" '(should have been 14313 characters)'
fi
fi # end of overwriting check
echo shar: "End of shell archive."
exit 0



More information about the Mod.sources mailing list