rs@uunet.UU.NET (Rich Salz) (07/08/87)
Mod.sources: Volume 10, Number 42 Submitted by: Doug Gwyn (VLD/VMB) <gwyn@brl.arpa> Archive-name: dir-lib.pch [ This was recently put out on comp.sources.bugs; it fixes a problem in V7 directories when the name is 14 characters long. I think Doug would agree with me that Amiga, MS-DOS, Minix, and other ports would be a great thing to have... --r$ ] #!/bin/sh # Self-unpacking archive format. To unbundle, sh this file. echo 'opendir.c' 1>&2 cat >'opendir.c' <<'END OF opendir.c' /* opendir -- open a directory stream last edit: 16-Jun-1987 D A Gwyn */ #include <sys/errno.h> #include <sys/types.h> #include <sys/stat.h> #include <dirent.h> #ifdef BSD_SYSV #define open _open /* avoid emulation overhead */ #endif typedef char *pointer; /* (void *) if you have it */ extern void free(); extern pointer malloc(); extern int open(), close(), fstat(); extern int errno; #ifndef NULL #define NULL 0 #endif #ifndef O_RDONLY #define O_RDONLY 0 #endif #ifndef S_ISDIR /* macro to test for directory file */ #define S_ISDIR( mode ) (((mode) & S_IFMT) == S_IFDIR) #endif DIR * opendir( dirname ) char *dirname; /* name of directory */ { register DIR *dirp; /* -> malloc'ed storage */ register int fd; /* file descriptor for read */ struct stat sbuf; /* result of fstat() */ if ( (fd = open( dirname, O_RDONLY )) < 0 ) return NULL; /* errno set by open() */ if ( fstat( fd, &sbuf ) != 0 || !S_ISDIR( sbuf.st_mode ) ) { (void)close( fd ); errno = ENOTDIR; return NULL; /* not a directory */ } if ( (dirp = (DIR *)malloc( sizeof(DIR) )) == NULL || (dirp->dd_buf = (char *)malloc( (unsigned)DIRBUF )) == NULL ) { register int serrno = errno; /* errno set to ENOMEM by sbrk() */ if ( dirp != NULL ) free( (pointer)dirp ); (void)close( fd ); errno = serrno; return NULL; /* not enough memory */ } dirp->dd_fd = fd; dirp->dd_loc = dirp->dd_size = 0; /* refill needed */ return dirp; } END OF opendir.c echo 'seekdir.c' 1>&2 cat >'seekdir.c' <<'END OF seekdir.c' /* seekdir -- reposition a directory stream last edit: 24-May-1987 D A Gwyn An unsuccessful seekdir() will in general alter the current directory position; beware. NOTE: 4.nBSD directory compaction makes seekdir() & telldir() practically impossible to do right. Avoid using them! */ #include <sys/errno.h> #include <sys/types.h> #include <dirent.h> extern off_t lseek(); extern int errno; #ifndef NULL #define NULL 0 #endif #ifndef SEEK_SET #define SEEK_SET 0 #endif typedef int bool; /* Boolean data type */ #define false 0 #define true 1 void seekdir( dirp, loc ) register DIR *dirp; /* stream from opendir() */ register off_t loc; /* position from telldir() */ { register bool rewind; /* "start over when stymied" flag */ if ( dirp == NULL || dirp->dd_buf == NULL ) { errno = EFAULT; return; /* invalid pointer */ } /* A (struct dirent)'s d_off is an invented quantity on 4.nBSD NFS-supporting systems, so it is not safe to lseek() to it. */ /* Monotonicity of d_off is heavily exploited in the following. */ /* This algorithm is tuned for modest directory sizes. For huge directories, it might be more efficient to read blocks until the first d_off is too large, then back up one block, or even to use binary search on the directory blocks. I doubt that the extra code for that would be worthwhile. */ if ( dirp->dd_loc >= dirp->dd_size /* invalid index */ || ((struct dirent *)&dirp->dd_buf[dirp->dd_loc])->d_off > loc /* too far along in buffer */ ) dirp->dd_loc = 0; /* reset to beginning of buffer */ /* else save time by starting at current dirp->dd_loc */ for ( rewind = true; ; ) { register struct dirent *dp; /* See whether the matching entry is in the current buffer. */ if ( (dirp->dd_loc < dirp->dd_size /* valid index */ || readdir( dirp ) != NULL /* next buffer read */ && (dirp->dd_loc = 0, true) /* beginning of buffer set */ ) && (dp = (struct dirent *)&dirp->dd_buf[dirp->dd_loc])->d_off <= loc /* match possible in this buffer */ ) { for ( /* dp initialized above */ ; (char *)dp < &dirp->dd_buf[dirp->dd_size]; dp = (struct dirent *)((char *)dp + dp->d_reclen) ) if ( dp->d_off == loc ) { /* found it! */ dirp->dd_loc = (char *)dp - dirp->dd_buf; return; } rewind = false; /* no point in backing up later */ dirp->dd_loc = dirp->dd_size; /* set end of buffer */ } else /* whole buffer past matching entry */ if ( !rewind ) { /* no point in searching further */ errno = EINVAL; return; /* no entry at specified loc */ } else { /* rewind directory and start over */ rewind = false; /* but only once! */ dirp->dd_loc = dirp->dd_size = 0; if ( lseek( dirp->dd_fd, (off_t)0, SEEK_SET ) != 0 ) return; /* errno already set (EBADF) */ if ( loc == 0 ) return; /* save time */ } } } END OF seekdir.c echo 'getdents.c' 1>&2 cat >'getdents.c' <<'END OF getdents.c' /* getdents -- get directory entries in a file system independent format (SVR3 system call emulation) last edit: 06-Jul-1987 D A Gwyn This single source file supports several different methods of getting directory entries from the operating system. Define whichever one of the following describes your system: UFS original UNIX filesystem (14-character name limit) BFS 4.2BSD (also 4.3BSD) native filesystem (long names) NFS getdirentries() system call Also define any of the following that are pertinent: ATT_SPEC check user buffer address for longword alignment BSD_SYSV BRL UNIX System V emulation environment on 4.nBSD UNK have _getdents() system call, but kernel may not support it If your C library has a getdents() system call interface, but you can't count on all kernels on which your application binaries may run to support it, change the system call interface name to _getdents() and define "UNK" to enable the system-call validity test in this "wrapper" around _getdents(). If your system has a getdents() system call that is guaranteed to always work, you shouldn't be using this source file at all. */ #include <sys/errno.h> #include <sys/types.h> #ifdef BSD_SYSV #include <sys/_dir.h> /* BSD flavor, not System V */ #else #include <sys/dir.h> #undef MAXNAMLEN /* avoid conflict with SVR3 */ /* Good thing we don't need to use the DIRSIZ() macro! */ #ifdef d_ino /* 4.3BSD/NFS using d_fileno */ #undef d_ino /* (not absolutely necessary) */ #else #define d_fileno d_ino /* (struct direct) member */ #endif #endif #include <sys/dirent.h> #include <sys/stat.h> #ifdef UNK #ifndef UFS #include "***** ERROR ***** UNK applies only to UFS" /* One could do something similar for getdirentries(), but I didn't bother. */ #endif #include <signal.h> #endif #if defined(UFS) + defined(BFS) + defined(NFS) != 1 /* sanity check */ #include "***** ERROR ***** exactly one of UFS, BFS, or NFS must be defined" #endif #ifdef UFS #define RecLen( dp ) (sizeof(struct direct)) /* fixed-length entries */ #else /* BFS || NFS */ #define RecLen( dp ) ((dp)->d_reclen) /* variable-length entries */ #endif #ifdef NFS #ifdef BSD_SYSV #define getdirentries _getdirentries /* package hides this system call */ #endif extern int getdirentries(); static long dummy; /* getdirentries() needs basep */ #define GetBlock( fd, buf, n ) getdirentries( fd, buf, (unsigned)n, &dummy ) #else /* UFS || BFS */ #ifdef BSD_SYSV #define read _read /* avoid emulation overhead */ #endif extern int read(); #define GetBlock( fd, buf, n ) read( fd, buf, (unsigned)n ) #endif #ifdef UNK extern int _getdents(); /* actual system call */ #endif extern char *strncpy(); extern int fstat(); extern off_t lseek(); extern int errno; #ifndef DIRBLKSIZ #define DIRBLKSIZ 4096 /* directory file read buffer size */ #endif #ifndef NULL #define NULL 0 #endif #ifndef SEEK_CUR #define SEEK_CUR 1 #endif #ifndef S_ISDIR /* macro to test for directory file */ #define S_ISDIR( mode ) (((mode) & S_IFMT) == S_IFDIR) #endif #ifdef UFS /* The following routine is necessary to handle DIRSIZ-long entry names. Thanks to Richard Todd for pointing this out. */ static int NameLen( name ) /* return # chars in embedded name */ char name[]; /* -> name embedded in struct direct */ { register char *s; /* -> name[.] */ register char *stop = &name[DIRSIZ]; /* -> past end of name field */ for ( s = &name[1]; /* (empty names are impossible) */ *s != '\0' /* not NUL terminator */ && ++s < stop; /* < DIRSIZ characters scanned */ ) ; return s - name; /* # valid characters in name */ } #else /* BFS || NFS */ extern int strlen(); #define NameLen( name ) strlen( name ) /* names are always NUL-terminated */ #endif #ifdef UNK static enum { maybe, no, yes } state = maybe; /* does _getdents() work? */ /*ARGSUSED*/ static void sig_catch( sig ) int sig; /* must be SIGSYS */ { state = no; /* attempted _getdents() faulted */ } #endif int getdents( fildes, buf, nbyte ) /* returns # bytes read; 0 on EOF, -1 on error */ int fildes; /* directory file descriptor */ char *buf; /* where to put the (struct dirent)s */ unsigned nbyte; /* size of buf[] */ { int serrno; /* entry errno */ off_t offset; /* initial directory file offset */ struct stat statb; /* fstat() info */ union { char dblk[DIRBLKSIZ]; /* directory file block buffer */ struct direct dummy; /* just for alignment */ } u; /* (avoids having to malloc()) */ register struct direct *dp; /* -> u.dblk[.] */ register struct dirent *bp; /* -> buf[.] */ #ifdef UNK switch ( state ) { void (*shdlr)(); /* entry SIGSYS handler */ register int retval; /* return from _getdents() if any */ case yes: /* _getdents() is known to work */ return _getdents( fildes, buf, nbyte ); case maybe: /* first time only */ shdlr = signal( SIGSYS, sig_catch ); retval = _getdents( fildes, buf, nbyte ); /* try it */ (void)signal( SIGSYS, shdlr ); if ( state == maybe ) /* SIGSYS did not occur */ { state = yes; /* so _getdents() must have worked */ return retval; } /* else fall through into emulation */ /* case no: /* fall through into emulation */ } #endif if ( buf == NULL #ifdef ATT_SPEC || (unsigned long)buf % sizeof(long) != 0 /* ugh */ #endif ) { errno = EFAULT; /* invalid pointer */ return -1; } if ( fstat( fildes, &statb ) != 0 ) return -1; /* errno set by fstat() */ if ( !S_ISDIR( statb.st_mode ) ) { errno = ENOTDIR; /* not a directory */ return -1; } if ( (offset = lseek( fildes, (off_t)0, SEEK_CUR )) < 0 ) return -1; /* errno set by lseek() */ #ifdef BFS /* no telling what remote hosts do */ if ( (unsigned long)offset % DIRBLKSIZ != 0 ) { errno = ENOENT; /* file pointer probably misaligned */ return -1; } #endif serrno = errno; /* save entry errno */ for ( bp = (struct dirent *)buf; bp == (struct dirent *)buf; ) { /* convert next directory block */ int size; do size = GetBlock( fildes, u.dblk, DIRBLKSIZ ); while ( size == -1 && errno == EINTR ); if ( size <= 0 ) return size; /* EOF or error (EBADF) */ for ( dp = (struct direct *)u.dblk; (char *)dp < &u.dblk[size]; dp = (struct direct *)((char *)dp + RecLen( dp )) ) { #ifndef UFS if ( dp->d_reclen <= 0 ) { errno = EIO; /* corrupted directory */ return -1; } #endif if ( dp->d_fileno != 0 ) { /* non-empty; copy to user buffer */ register int reclen = DIRENTSIZ( NameLen( dp->d_name ) ); if ( (char *)bp + reclen > &buf[nbyte] ) { errno = EINVAL; return -1; /* buf too small */ } bp->d_ino = dp->d_fileno; bp->d_off = offset + ((char *)dp - u.dblk); bp->d_reclen = reclen; (void)strncpy( bp->d_name, dp->d_name, reclen - DIRENTBASESIZ ); /* adds NUL padding */ bp = (struct dirent *)((char *)bp + reclen); } } #ifndef BFS /* 4.2BSD screwed up; fixed in 4.3BSD */ if ( (char *)dp > &u.dblk[size] ) { errno = EIO; /* corrupted directory */ return -1; } #endif } errno = serrno; /* restore entry errno */ return (char *)bp - buf; /* return # bytes read */ } END OF getdents.c -- Rich $alz "Anger is an energy" Cronus Project, BBN Labs rsalz@pineapple.bbn.com Moderator, comp.sources.unix sources@uunet.uu.net