|
DataMuseum.dkPresents historical artifacts from the history of: DKUUG/EUUG Conference tapes |
This is an automatic "excavation" of a thematic subset of
See our Wiki for more about DKUUG/EUUG Conference tapes Excavated with: AutoArchaeologist - Free & Open Source Software. |
top - downloadIndex: ┃ T s ┃
Length: 14319 (0x37ef) Types: TextFile Names: »sftw.c«
└─⟦a0efdde77⟧ Bits:30001252 EUUGD11 Tape, 1987 Spring Conference Helsinki └─ ⟦this⟧ »EUUGD11/euug-87hel/sec1/hier/sftw.c«
/* * Sorted file tree walk (library routine). * * Identical (in theory) to ftw(3), except: * * - Calls user's fn() with the files sorted alphabetically (per strcmp(3)) * in two groups: All non-directories first, followed by directories (with * the descendents of each directory after the directory). Non-stat'able * files and non-readable directories are included in the second group. * * - Doesn't keep one file open for each level of recursion, so it doesn't * need a depth argument (which actually affects file opens/closes, NOT * maximum search depth). * * - Uses a lot more malloc space. * * - Supports an additional argument which tells it to include all files * and directories, including those whose names start with "." (except that * the given filename is always included, regardless of the flag, like * ls(1)). The caller could implement this, but not very efficiently. * * Like ftw(), it ignores "." and ".." files, even with the all flag. * * For convenience, form of call is: * * #include <ftw.h> * * int sftw (path, fn, allfiles) * char *path; * int (*fn)(); * int allfiles; * * Form of fn() is: * * int fn (name, statp, type) * char *name; * struct stat *statp; * int type; * * See ftw(3) for more information. * * Compile with -DTEST to get a runnable test version that walks from "." * and tells types, permissions, and filenames passed to fn(). */ #include <sys/types.h> #include <sys/stat.h> #include <ftw.h> #ifdef BSD #include <sys/dir.h> #else #include <ndir.h> #endif /* BSD */ static char *malloc(), *strcpy(); static void free(); /********************************************************************* * MISCELLANEOUS GLOBAL VALUES: */ #define PROC /* null; easy to find procs */ #define FALSE 0 #define TRUE 1 #define CHNULL ('\0') #define CPNULL ((char *) NULL) #define REG register /* built-up filename for passing to the user program; hope it's big enough */ static char filename [1000]; static unsigned short euid, egid; /* only get them once */ /************************************************************************ * FILE DATA STRUCTURE: * * A contiguous array of pointers is used for sorting, built after knowing * how many directory entries there are to sort. Each entry points to a * struct filedata which holds information for one directory entry. */ typedef struct filedata *fdpt; typedef struct filedata **fdppt; struct filedata { char *name; /* in malloc'd space */ int type; /* see ftw.h */ struct stat statbuf; /* from stat(2) */ }; /************************************************************************ * FILE AND STRING DATA BLOCKS: * * Since a directory may grow arbitrarily as it's read, there's no way to * know in advance how big it is. And it's necessary to return all malloc'd * memory. To make this possible, and to save malloc space and time, directory * entry data and filenames are stored in buffers allocated a chunk a time. */ #define DBENTRIES 20 /* file entries per datablock */ #define STRINGENTRIES 1008 /* chars per string buffer */ typedef struct datablock *dbpt; typedef struct datablock **dbppt; struct datablock { dbpt next; /* next block if any */ struct filedata fd [DBENTRIES]; /* the data itself */ }; #define DBNULL ((dbpt) NULL) #define DBSIZE (sizeof (struct datablock)) typedef struct stringblock *sbpt; typedef struct stringblock **sbppt; struct stringblock { sbpt next; /* next block if any */ char buf [STRINGENTRIES]; /* the data itself */ }; #define SBNULL ((sbpt) NULL) #define SBSIZE (sizeof (struct stringblock)) /************************************************************************ * S F T W * * Handle the filename given by the user. Since sftw() must stat() each * file before sorting, the first (top level) file must be handled specially, * not as part of re-entrant code. (Think about it...) */ PROC int sftw (path, UserFunc, allfiles) char *path; int (*UserFunc)(); int allfiles; { struct stat statbuf; /* from first file */ int type; /* of first file */ int retval; /* return by UserFunc() */ unsigned short geteuid(), getegid(); euid = geteuid(); /* initialize values */ egid = getegid(); /* * HANDLE INITIAL FILE: */ type = GetType (path, & statbuf); if (retval = (*UserFunc) (path, & statbuf, type)) /* it's unhappy */ return (retval); if (type != FTW_D) /* we're done */ return (0); /* * WORK ON A READABLE DIRECTORY: */ strcpy (filename, path); /* now we can append to it */ strcat (filename, "/"); /* prepare for additions */ return (DoDirectory (UserFunc, allfiles)); } /* sftw */ /************************************************************************ * D O D I R E C T O R Y * * Given UserFunc(), all files flag, and global filename (directory path) where * to start and on which to build complete pathnames, read the directory, sort * filenames, and call UserFunc() for each file in the directory. This routine * calls itself to recurse, after each directory name is passed to UserFunc(). * Because it reads and saves a directory's contents in order to sort them, it * does not keep any files open while recursing, just lots of memory. * * Free all memory from this level before returning, even in case of error. * Return -1 in case of error, or the value from UserFunc() if non-zero. */ PROC static int DoDirectory (UserFunc, allfiles) int (*UserFunc)(); int allfiles; /* include ".*" files? */ { dbpt dbhead = DBNULL; /* first datablock ptr */ sbpt sbhead = SBNULL; /* first stringblock ptr */ fdppt fdphead; /* filedata list to sort */ REG fdppt fdpcurr; /* current list pointer */ REG fdpt fdcurr; /* current entry pointer */ REG int files; /* number in directory */ int retval; /* copy of return value */ /* pointer into filename where to append basenames */ REG char *basename = filename + strlen (filename); int FDCmp(); void qsort(); #define RETURN(value) { FreeBlocks (dbhead, sbhead); return (value); } /* * READ DIRECTORY: */ if ((files = ReadDirectory (& dbhead, & sbhead, allfiles)) < 0) RETURN (-1); /* * BUILD AND SORT POINTERS TO FILES: * * Get a big chunk of contiguous memory for the pointers, then set them up. * Afterwards, filedata entries will be accessed via the pointers. */ if ((fdphead = (fdppt) malloc (files * sizeof (fdpt))) == (fdppt) NULL) RETURN (-1); #undef RETURN #define RETURN(value) { FreeBlocks (dbhead, sbhead); \ free ((char *) fdphead); return (value); } SetFDList (fdphead, fdphead + files, dbhead); qsort ((char *) fdphead, (unsigned) files, sizeof (fdpt), FDCmp); /* * TRAVERSE FILES USING SORTED POINTERS: * * Append each file's basename to the current path in global filename, * overlaying whatever basename was there before, and pass it to UserFunc(). */ fdpcurr = fdphead; while (files-- > 0) { strcpy (basename, (fdcurr = (*fdpcurr++)) -> name); if (retval = (*UserFunc) (filename, & (fdcurr -> statbuf), fdcurr -> type)) /* it's unhappy */ { RETURN (retval); } /* * RECURSE FOR A DIRECTORY: */ if ((fdcurr -> type) == FTW_D) { strcat (basename, "/"); /* for next level */ if (retval = DoDirectory (UserFunc, allfiles)) RETURN (retval); } } RETURN (0); } /* DoDirectory */ /************************************************************************ * R E A D D I R E C T O R Y * * Given pointers to datablock and stringblock chain head pointers, all files * flag, and global filename (name of a readable directory) on which to build * complete pathnames, open, read, and close a directory, saving name, stat, * and type information on each file in a chain of datablocks and stringblocks, * and setting the chain head pointers. Return the number of filenames read * and saved (>= 0), or -1 in case of error, but always close the directory. */ PROC static int ReadDirectory (dbheadp, sbheadp, allfiles) dbppt dbheadp; /* start of chain */ sbppt sbheadp; /* start of chain */ int allfiles; /* include ".*" files? */ { REG DIR *dirp; /* for reading directory */ REG struct direct *entp; /* directory entry */ REG char *name; /* fast copy of filename */ REG dbpt dbcurr; /* current datablock ptr */ dbppt dbnextp = dbheadp; /* next datablock ptr ptr */ REG int dbentry = DBENTRIES; /* current entry in block */ REG fdpt fdcurr; /* current filedata entry */ REG sbpt sbcurr; /* current stringblock ptr */ sbppt sbnextp = sbheadp; /* next stringblock ptr ptr */ REG char *end = ""; /* last + 1 of block */ REG char *start = end; /* next free place */ /* pointer into filename where to append basenames */ REG char *basename = filename + strlen (filename); REG int files = 0; /* files read and saved */ #undef RETURN #define RETURN(value) { closedir (dirp); return (value); } /* * OPEN AND READ DIRECTORY: */ if ((dirp = opendir (filename)) == (DIR *) NULL) return (-1); /* hope errno is set */ /* now be sure to use the RETURN() macro */ while ((entp = readdir (dirp)) != (struct direct *) NULL) { /* * OPTIONALLY SKIP ".*" FILES: * * Always skip "." and ".." files, like ftw(). */ if ((* (name = entp -> d_name) == '.') /* fast check */ && ((! allfiles) || (name [1] == CHNULL) || ((name [1] == '.') && (name [2] == CHNULL)))) { continue; } files++; /* * GET A NEW DATABLOCK; APPEND TO CHAIN: */ if (dbentry >= DBENTRIES) /* block is full */ { if ((dbcurr = *dbnextp = (dbpt) malloc (DBSIZE)) == DBNULL) RETURN (-1); * (dbnextp = & (dbcurr -> next)) = DBNULL; dbentry = 0; } /* * GET A NEW STRINGBLOCK; APPEND TO CHAIN: * * Yes, we may abandon some unused space in the previous block... Hope that * STRINGENTRIES is much larger than the average directory entry name size. */ if ((entp -> d_namlen) + 1 > (end - start)) { if ((sbcurr = *sbnextp = (sbpt) malloc (SBSIZE)) == SBNULL) RETURN (-1); * (sbnextp = & (sbcurr -> next)) = SBNULL; end = (start = (sbcurr -> buf)) + STRINGENTRIES; } /* * SAVE INFORMATION ON ONE FILE: * * Append each file's basename to the current path in global filename, * overlaying whatever basename was there before, and pass it to GetType(). */ fdcurr = (dbcurr -> fd) + (dbentry++); /* quick pointer */ strcpy (((fdcurr -> name) = start), name); start += (entp -> d_namlen) + 1; strcpy (basename, name); (fdcurr -> type) = GetType (filename, & (fdcurr -> statbuf)); } /* while */ RETURN (files); } /* ReadDirectory */ /************************************************************************ * G E T T Y P E * * Given a filename and a pointer to a stat structure, stat() the file into the * structure and return an appropriate ftw() type. Since directories are not * opened for reading until much later, after sorting, determine readability * now using euid, egid, and st_mode. Can't use access(2) because it checks * real ids, not effective (sigh). */ PROC static int GetType (name, statp) char *name; struct stat *statp; { #define UREAD (S_IREAD) /* read permission bits for user, group, other */ #define GREAD (S_IREAD >> 3) #define OREAD (S_IREAD >> 6) if (stat (name, statp) < 0) return (FTW_NS); /* not stat'able */ if (((statp -> st_mode) & S_IFMT) != S_IFDIR) return (FTW_F); /* not directory */ /* pick appropriate permission bit, then see if it's set */ return ( ( ((statp -> st_uid) == euid) ? ((statp -> st_mode) & UREAD) : ((statp -> st_gid) == egid) ? ((statp -> st_mode) & GREAD) : ((statp -> st_mode) & OREAD) ) ? FTW_D : FTW_DNR); } /* GetType */ /************************************************************************ * S E T F D L I S T * * Given pointers to the current (head) and end + 1 of an array of * uninitialized struct filedata pointers, and a current (head) struct * datablock pointer, fill in the pointers in the array to point to the * filedata entries in the datablock chain. */ PROC static SetFDList (fdpcurr, fdpend, dbcurr) REG fdppt fdpcurr; /* start at head */ REG fdppt fdpend; /* last + 1 */ REG dbpt dbcurr; /* start at head */ { REG int dbentry; /* current index */ while (TRUE) /* until return */ { for (dbentry = 0; dbentry < DBENTRIES; dbentry++) /* one block */ { if (fdpcurr >= fdpend) /* no more */ return; *fdpcurr++ = (dbcurr -> fd) + dbentry; } dbcurr = dbcurr -> next; } /* never get here */ } /* SetFDList */ /************************************************************************ * F D C M P * * Given two pointers to pointers to filedata entries, compare the entries * and return -1, 0, or 1 for how they relate. "Normal" files (FTW_F) * are always lower than other types, then names are compared with strcmp(). */ PROC static int FDCmp (fdpp1, fdpp2) fdppt fdpp1, fdpp2; { REG int type1 = (*fdpp1) -> type; REG int type2 = (*fdpp2) -> type; return (((type1 == FTW_F) && (type2 != FTW_F)) ? -1 : ((type1 != FTW_F) && (type2 == FTW_F)) ? 1 : strcmp ((*fdpp1) -> name, (*fdpp2) -> name)); } /* FDCmp */ /************************************************************************ * F R E E B L O C K S * * Given pointers to heads of datablock and stringblock chains, free the * malloc'd memory in the chains. */ PROC static FreeBlocks (dbhead, sbhead) REG dbpt dbhead; REG sbpt sbhead; { REG dbpt dbtemp; REG sbpt sbtemp; while (dbhead != DBNULL) { dbtemp = dbhead -> next; free ((char *) dbhead); dbhead = dbtemp; } while (sbhead != SBNULL) { sbtemp = sbhead -> next; free ((char *) sbhead); sbhead = sbtemp; } } /* FreeBlocks */ #ifdef TEST /************************************************************************ * TEST HARNESS: */ PROC int fn (name, statp, type) char *name; struct stat *statp; int type; { printf ("%-3s %06o \"%s\"\n", (type == FTW_F) ? "F" : (type == FTW_D) ? "D" : (type == FTW_DNR) ? "DNR" : (type == FTW_NS) ? "NS" : "?", statp -> st_mode, name); return (0); } PROC main() { printf ("sftw returns %d\n", sftw (".", fn)); } #endif TEST