[gnu.g++.lib.bug] Directory access routines in C++

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