schmidt@zola.ics.uci.edu (Doug Schmidt) (12/03/89)
While writing a fast directory listing program (included in ./etc in the upcoming libg++ beta release) I decided to clean-up the interface to the directory-access routines (opendir/closedir/readdir/etc) by making them member functions in a C++ class. The routines included below are based upon Doug Gwyn's portable public domain C directory access library. The C++ class rewrite works nicely on the Sun 4's right now. If anyone would like to add support for other systems please feel free to migrate the changes back to me. thanks, Doug p.s. Doug Gwyn's original library is available from uunet.uu.net in the comp.sources.unix subdirectory, volume9 and volume10. ---------------------------------------- #! /bin/sh # This is a shell archive. Remove anything before this line, then unpack # it by saving it into a file and typing "sh file". To overwrite existing # files, type "sh file -c". You can also feed this as standard input via # unshar, or by typing "sh <file", e.g.. If this archive is complete, you # will see the following message at the end: # "End of shell archive." # Contents: Dirent.cc Dirent.h testdir.c # Wrapped by schmidt@zola.ics.uci.edu on Sat Dec 2 18:02:41 1989 PATH=/bin:/usr/bin:/usr/ucb ; export PATH if test -f 'Dirent.cc' -a "${1}" != "-c" ; then echo shar: Will not clobber existing file \"'Dirent.cc'\" else echo shar: Extracting \"'Dirent.cc'\" \(13396 characters\) sed "s/^X//" >'Dirent.cc' <<'END_OF_FILE' X/* Define a portable UNIX directory-entry manipulation interface. X X This code is heavily based upon Doug Gwyn's public domain directory-access X routines. Hacked into C++ conformance by Doug Schmidt (schmidt@ics.uci.edu). */ X X#include <unistd.h> X#include <std.h> X#include <fcntl.h> X#include "Dirent.h" X X/* Initialize the directory-entry control block from the given DIRNAME. X Equivalent to opendir (). */ X XDirent::Dirent (char *dirname) X{ X int fd; /* file descriptor for read */ X struct stat sbuf; /* result of fstat() */ X X if ((fd = open (dirname, O_RDONLY)) < 0) X { X /* need some kind of error handler? */ X perror ("Dirent::Dirent"); X return; X } X X if (fstat (fd, &sbuf) != 0 || !is_dir (sbuf.st_mode)) X { X close (fd); X /* need some kind of error handler? */ X perror ("Dirent::Dirent"); X return; X } X X if ((direntry.dd_buf = new char[DIRBUF]) == 0) X { X close (fd); X /* need some kind of error handler? */ X perror ("Dirent::Dirent"); X return; X } X X direntry.dd_fd = fd; X direntry.dd_loc = direntry.dd_size = 0; /* refill needed */ X} X X/* Initialize the directory-entry control block from the given FILDES. */ X XDirent::Dirent (int fildes) X{ X struct stat sbuf; /* result of fstat() */ X X if (fstat (fildes, &sbuf) != 0 || !is_dir (sbuf.st_mode)) X { X close (fildes); X /* need some kind of error handler? */ X perror ("Dirent::Dirent"); X return; X } X X if ((direntry.dd_buf = new char[DIRBUF]) == 0) X { X close (fildes); X /* need some kind of error handler? */ X perror ("Dirent::Dirent"); X return; X } X X direntry.dd_fd = fildes; X direntry.dd_loc = direntry.dd_size = 0; /* refill needed */ X} X X/* Frees up the dynamically allocated buffer. */ X XDirent::~Dirent (void) X{ X delete direntry.dd_buf; X close (direntry.dd_fd); X} X X/* Re-initialize the directory-entry control block from the given DIRNAME. X Equivalent to opendir () (note that we don't bother reallocating X space for field dd_buf). */ X X void X Dirent::opendir (char *dirname) X{ X int fd; /* file descriptor for read */ X struct stat sbuf; /* result of fstat() */ X X if ((fd = open (dirname, O_RDONLY)) < 0) X { X /* need some kind of error handler? */ X perror ("Dirent::Dirent"); X return; X } X X if (fstat (fd, &sbuf) != 0 || !is_dir (sbuf.st_mode)) X { X close (fd); X /* need some kind of error handler? */ X perror ("Dirent::Dirent"); X return; X } X X direntry.dd_fd = fd; X direntry.dd_loc = direntry.dd_size = 0; /* refill needed */ X} X X/* Re-initialize the directory-entry control block from the given FILDES. */ X Xvoid XDirent::opendir (int fildes) X{ X struct stat sbuf; /* result of fstat() */ X X if (fstat (fildes, &sbuf) != 0 || !is_dir (sbuf.st_mode)) X { X close (fildes); X /* need some kind of error handler? */ X perror ("Dirent::Dirent"); X return; X } X X direntry.dd_fd = fildes; X direntry.dd_loc = direntry.dd_size = 0; /* refill needed */ X} X X/* Read next entry from a directory stream. */ X Xstruct direct * XDirent::readdir (void) X{ X struct direct *dp; /* -> directory data */ X X do X { X if (direntry.dd_loc >= direntry.dd_size) /* empty or obsolete */ X direntry.dd_loc = direntry.dd_size = 0; X X /* need to refill buffer */ X if (direntry.dd_size == 0 X && (direntry.dd_size = X getdents (direntry.dd_fd, direntry.dd_buf, DIRBUF)) <= 0) X return 0; /* EOF or error */ X X dp = (struct direct *) &direntry.dd_buf[direntry.dd_loc]; X direntry.dd_loc += dp->d_reclen; X } X while (dp->d_ino == 0L) ; /* don't rely on getdents() */ X X return dp; X} X X/* Close a directory stream. */ X Xint XDirent::closedir (void) X{ X delete direntry.dd_buf; X return close (direntry.dd_fd); X} X X/* Rewind a directory stream. */ X Xvoid XDirent::rewinddir (void) X{ X direntry.dd_loc = direntry.dd_size = 0; /* invalidate buffer */ X lseek (direntry.dd_fd, (off_t) 0, SEEK_SET); /* may set errno */ X} X X/* Reposition a directory stream. */ X Xvoid XDirent::seekdir (off_t loc) X{ X int rewind; /* "start over when stymied" flag */ X X /* A (struct direct)'s d_off is an invented quantity on 4.nBSD X NFS-supporting systems, so it is not safe to lseek() to it. */ X X /* Monotonicity of d_off is heavily exploited in the following. */ X X /* This algorithm is tuned for modest directory sizes. For X huge directories, it might be more efficient to read blocks X until the first d_off is too large, then back up one block, X or even to use binary search on the directory blocks. I X doubt that the extra code for that would be worthwhile. */ X X if (direntry.dd_loc >= direntry.dd_size /* invalid index */ X || ((struct direct *) & direntry.dd_buf[direntry.dd_loc])->d_off > loc) X direntry.dd_loc = 0; /* reset to beginning of buffer */ X X /* else save time by starting at current direntry.dd_loc */ X X for (rewind = 1;;) X { X struct direct *dp; X X /* See whether the matching entry is in the current buffer. */ X X if ((direntry.dd_loc < direntry.dd_size || readdir () != 0 X && (direntry.dd_loc = 0, 1)) X && (dp = (struct direct *) & direntry.dd_buf[direntry.dd_loc])->d_off <= loc ) X { X for ( /* dp initialized above */ ; X (char *) dp < &direntry.dd_buf[direntry.dd_size]; X dp = (struct direct *) ((char *) dp + dp->d_reclen)) X if (dp->d_off == loc) X { /* found it! */ X direntry.dd_loc = (char *) dp - direntry.dd_buf; X return; X } X X rewind = 0; /* no point in backing up later */ X direntry.dd_loc = direntry.dd_size; /* set end of buffer */ X } X else if (!rewind) /* no point in searching further */ X return; /* no entry at specified loc */ X else X { /* rewind directory and start over */ X rewind = 0; /* but only once! */ X X direntry.dd_loc = direntry.dd_size = 0; X X if (lseek (direntry.dd_fd, (off_t) 0, SEEK_SET)!= 0) X return; /* errno already set (EBADF) */ X X if (loc == 0) X return; /* save time */ X } X } X} X X/* Report directory stream position. */ X Xoff_t XDirent::telldir (void) /* return offset of next entry */ X{ X if (direntry.dd_loc < direntry.dd_size) X return ((struct direct *) &direntry.dd_buf[direntry.dd_loc])->d_off; X else X return lseek (direntry.dd_fd, (off_t) 0, SEEK_CUR); X} X X#if defined (sun) Xextern "C" int getdents (int, char *, unsigned); X Xint XDirent::getdents (int fildes, char *buf, unsigned nbyte) X{ X return ::getdents (fildes, buf, nbyte); X} X#else X X#if defined (USG) X#define UFS X#else X#define BFS X#endif X X#include <sys/errno.h> X X#undef MAXNAMLEN /* avoid conflict with SVR3 */ X X#include <sys/stat.h> X#ifdef UNK X#include <signal.h> X#endif X X#ifdef UFS X#define RecLen( dp ) (sizeof(struct direct)) /* fixed-length entries */ X#else /* BFS || NFS */ X#define RecLen( dp ) ((dp)->d_reclen) /* variable-length entries */ X#endif X X#ifdef NFS Xextern int getdirentries (int, char *, int, long *); Xstatic long dummy; /* getdirentries() needs basep */ X#define GetBlock( fd, buf, n ) getdirentries( fd, buf, (unsigned)n, &dummy ) X#else /* UFS || BFS */ X#define GetBlock( fd, buf, n ) read( fd, buf, (unsigned)n ) X#endif X X#ifdef UNK Xextern int _getdents (); /* actual system call */ X#endif X X#ifndef DIRBLKSIZ X#define DIRBLKSIZ 4096 /* directory file read buffer size */ X#endif X X/* The following nonportable ugliness could have been avoided by defining X DIRENTSIZ and DIRENTBASESIZ to also have (struct direct *) arguments. */ X#define DIRENTBASESIZ (((struct direct *)0)->d_name \ X - (char *)&((struct direct *)0)->d_ino) X#define DIRENTSIZ( namlen ) ((DIRENTBASESIZ + sizeof(long) + (namlen)) \ X / sizeof(long) * sizeof(long)) X X#ifndef S_ISDIR /* macro to test for directory file */ X#define S_ISDIR( mode ) (((mode) & S_IFMT) == S_IFDIR) X#endif X X#ifdef UFS X X/* X The following routine is necessary to handle DIRSIZ-long entry names. X Thanks to Richard Todd for pointing this out. X*/ X Xstatic int XNameLen (char name[]); /* -> name embedded in struct direct */ X{ X register char *s; /* -> name[.] */ X register char *stop = &name[DIRSIZ]; /* -> past end of name field */ X X for (s = &name[1]; /* (empty names are impossible) */ X *s != '\0' /* not NUL terminator */ X && ++s < stop; /* < DIRSIZ characters scanned */ X ) X ; X X return s - name; /* # valid characters in name */ X} X X#else /* BFS || NFS */ X Xextern int strlen (); X X#define NameLen( name ) strlen( name ) /* names are always NUL-terminated */ X X#endif X X#ifdef UNK Xstatic enum X{ X maybe, no, yes X} state = maybe; X X /* does _getdents() work? */ X X/*ARGSUSED*/ Xstatic void Xsig_catch (int sig); /* must be SIGSYS */ X{ X state = no; /* attempted _getdents() faulted */ X} X X#endif X X/* returns # bytes read; 0 on EOF, -1 on error */ X Xint XDirent::getdents (int fildes, char *buf, unsigned nbyte) X{ X int serrno; /* entry errno */ X off_t offset; /* initial directory file offset */ X struct stat statb; /* fstat() info */ X union X { X char dblk[DIRBLKSIZ]; X /* directory file block buffer */ X struct direct dummy; /* just for alignment */ X } u; /* (avoids having to malloc()) */ X register struct direct *dp; /* -> u.dblk[.] */ X register struct direct *bp; /* -> buf[.] */ X X#ifdef UNK X switch (state) X { X void (*shdlr) (); /* entry SIGSYS handler */ X register int retval; /* return from _getdents() if any */ X X case yes: /* _getdents() is known to work */ X return _getdents (fildes, buf, nbyte); X X case maybe: /* first time only */ X shdlr = signal (SIGSYS, sig_catch); X retval = _getdents (fildes, buf, nbyte); /* try it */ X (void) signal (SIGSYS, shdlr); X X if (state == maybe) /* SIGSYS did not occur */ X { X state = yes; /* so _getdents() must have worked */ X return retval; X } X /* else fall through into emulation */ X X /* case no: /* fall through into emulation */ X } X#endif X X if (buf == 0 X#ifdef ATT_SPEC X || (unsigned long) buf % sizeof (long) != 0 /* ugh */ X#endif X ) X { X errno = EFAULT; /* invalid pointer */ X return -1; X } X X if (fstat (fildes, &statb) != 0) X return -1; /* errno set by fstat() */ X X if (!S_ISDIR (statb.st_mode)) X { X errno = ENOTDIR; /* not a directory */ X return -1; X } X X if ((offset = lseek (fildes, (off_t) 0, SEEK_CUR)) < 0) X return -1; /* errno set by lseek() */ X X#ifdef BFS /* no telling what remote hosts do */ X if ((unsigned long) offset % DIRBLKSIZ != 0) X { X errno = ENOENT; /* file pointer probably misaligned */ X return -1; X } X#endif X X serrno = errno; /* save entry errno */ X X for (bp = (struct direct *) buf; bp == (struct direct *) buf;) X { /* convert next directory block */ X int size; X X do size = GetBlock (fildes, u.dblk, DIRBLKSIZ); X while (size == -1 && errno == EINTR) ; X X if (size <= 0) X return size; /* EOF or error (EBADF) */ X X for (dp = (struct direct *) u.dblk; X (char *) dp < &u.dblk[size]; X dp = (struct direct *) ((char *) dp + RecLen (dp)) X ) X { X#ifndef UFS X if (dp->d_reclen <= 0) X { X errno = EIO; /* corrupted directory */ X return -1; X } X#endif X X if (dp->d_fileno != 0) X { /* non-empty; copy to user buffer */ X register int reclen = X DIRENTSIZ (NameLen (dp->d_name)); X X if ((char *) bp + reclen > &buf[nbyte]) X { X errno = EINVAL; X return -1; /* buf too small */ X } X X bp->d_ino = dp->d_fileno; X bp->d_off = offset + ((char *) dp - u.dblk); X bp->d_reclen = reclen; X (void) strncpy (bp->d_name, dp->d_name, X reclen - DIRENTBASESIZ X ); /* adds NUL padding */ X X bp = (struct direct *) ((char *) bp + reclen); X } X } X X#ifndef BFS /* 4.2BSD screwed up; fixed in 4.3BSD */ X if ((char *) dp > &u.dblk[size]) X { X errno = EIO; /* corrupted directory */ X return -1; X } X#endif X } X X errno = serrno; /* restore entry errno */ X return (char *) bp - buf; /* return # bytes read */ X} X X#endif /* defined (USG) */ END_OF_FILE if test 13396 -ne `wc -c <'Dirent.cc'`; then echo shar: \"'Dirent.cc'\" unpacked with wrong size! fi # end of 'Dirent.cc' fi if test -f 'Dirent.h' -a "${1}" != "-c" ; then echo shar: Will not clobber existing file \"'Dirent.h'\" else echo shar: Extracting \"'Dirent.h'\" \(984 characters\) sed "s/^X//" >'Dirent.h' <<'END_OF_FILE' X/* Define a portable UNIX directory-entry manipulation interface. X X This code is heavily based upon Doug Gwyn's public domain directory-access X routines. Hacked into C++ conformance by Doug Schmidt (schmidt@ics.uci.edu). */ X X#include <sys/types.h> X#include <sys/stat.h> X/* This should probably be added to libg++'s g++-include/sys directory... */ Xextern "C" X{ X#include <sys/dir.h> X} X X#undef rewinddir X Xclass Dirent X{ Xprivate: X DIR direntry; X const int DIRBUF = 8192; X inline int is_dir (int mode) { return ((mode) & S_IFMT) == S_IFDIR; } X int getdents (int fildes, char *buf, unsigned nbyte); X Xpublic: X Dirent (char *dirname); X Dirent (int fildes); X ~Dirent (void); X struct direct *readdir (void); X void opendir (char *filename); X void opendir (int fildes); X int closedir (void); X off_t telldir (void); X void seekdir (off_t loc); X void rewinddir (void); X}; END_OF_FILE if test 984 -ne `wc -c <'Dirent.h'`; then echo shar: \"'Dirent.h'\" unpacked with wrong size! fi # end of 'Dirent.h' fi if test -f 'testdir.c' -a "${1}" != "-c" ; then echo shar: Will not clobber existing file \"'testdir.c'\" else echo shar: Extracting \"'testdir.c'\" \(316 characters\) sed "s/^X//" >'testdir.c' <<'END_OF_FILE' X#include <stdio.h> X#include <std.h> X#include "Dirent.h" X Xmain (int argc, char **argv) X{ X Dirent dir ("."); X struct direct *dp; X X while ((dp = dir.readdir ()) != NULL) X printf ("%s\n", dp->d_name); X X dir.opendir (".."); X X while ((dp = dir.readdir ()) != NULL) X printf ("%s\n", dp->d_name); X X return 0; X} END_OF_FILE if test 316 -ne `wc -c <'testdir.c'`; then echo shar: \"'testdir.c'\" unpacked with wrong size! fi # end of 'testdir.c' fi echo shar: End of shell archive. exit 0 -- Any man's death diminishes me, | schmidt@ics.uci.edu (ARPA) Because I am involved in Mankind; | office: (714) 856-4043 And therefore never send to know for whom the bell tolls; It tolls for thee -- John Donne