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