sources-request@mirror.UUCP (02/12/87)
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