gwyn@BRL.ARPA (05/28/87)
install INSTALLATION INSTRUCTIONS The following instructions are specifically for Manx Software Systems Aztec C running under ProDOS (C65 V3.20B). This package should be adaptable to other ProDOS C environments with relatively little trouble. Installation should be done only by someone who is comfortable with modifying the standard C library and header files. I assume that you have environment variables CLIB and INCLUDE set up to refer to the system library and header directories. DISCLAIMER: Although I believe the code and procedures described here to be correct, I make no warranty of any kind, and you are advised to perform your own careful testing before making any substantial change like this to your programming environment. 0) Unpack the archive in an empty subdirectory; assuming you saved it (minus any header and trailer added by mail systems) in a file named "gwyn.arc": $ arcv gwyn.arc If this does not work, use a text editor to hack apart the files; the divisions and filenames should be obvious. 1) Install the header files: $ unlock $INCLUDE $ cp dirent.h $INCLUDE $ mkdir $INCLUDE/sys $ cp sys.dir.h $INCLUDE/sys/dir.h $ cp sys.dirent.h $INCLUDE/sys/dirent.h $ cp sys.types.h $INCLUDE/sys/types.h 2) Update the C libraries: $ unlock $CLIB/c.lib $CLIB/ci.lib $ cc closedir.c $ cc getdents.c $ cc opendir.c $ cc readdir.c $ cc rewinddir.c $ cc seekdir.c $ cc telldir.c $ echo *.o >xlist $ ord xlist olist $ rm xlist $ lb $CLIB/c.lib -b+ -f olist $ rm *.o olist $ cci closedir.c $ cci getdents.c $ cci opendir.c $ cci readdir.c $ cci rewinddir.c $ cci seekdir.c $ cci telldir.c $ echo *.i >xlist $ ord xlist ilist $ rm xlist $ lb $CLIB/ci.lib -b+ -f ilist $ rm *.i ilist 3) To verify installation, try compiling, linking, and running the program testdir.c: $ cc testdir.c $ ln testdir.o -lc $ rm testdir.o This program searches the current directory "." for each file named as a program argument and prints `"FOO" found.' or `"FOO" not found.' where FOO is of course replaced by the name being sought in the directory. Try something like $ testdir foo testdir bar dirent.h xyzzy which should produce the output "foo" not found. "testdir" found. "bar" not found. "dirent.h" found. "xyzzy" not found. This program does not test the seekdir() and telldir() functions. 4) I have also included the freebie utility, listdir.c. This program acts like a plain vanilla "ls ." (one entry per line). Build it by invoking: $ cc listdir.c $ ln listdir.o -lc $ rm listdir.o Feel free to hack on this but please DON'T send me your improvements! 5) The file "notes" explains what this package is all about. I posted the UNIX manual entries (usage documentation) in an earlier separate message. 6) In case of difficulty, you can TRY to reach me by e-mail, but I don't guarantee I'll respond (it will depend on the message volume). Remember that this is free software and you shouldn't expect much support at these prices. - Gwyn@BRL.MIL notes NOTES FOR NEARLY-POSIX-COMPATIBLE C LIBRARY DIRECTORY-ACCESS ROUTINES Older UNIX C libraries lacked support for reading directories, so historically programs had knowledge of UNIX directory structure hard-coded into them. When Berkeley changed the format of directories for 4.2BSD, it became necessary to change programs to work with the new structure. Fortunately, Berkeley designed a small set of directory access routines to encapsulate knowledge of the new directory format so that user programs could deal with directory entries as an abstract data type. (Unfortunately, they didn't get it quite right.) The interface to these routines was nearly independent of the particular implementation of directories on any given UNIX system; this has become a particularly important requirement with the advent of heterogeneous network filesystems such as NFS. The availability of C for the Apple II has prompted me to extend this package beyond the UNIX domain; the system- independent interface works well for this environment also. It has consequently become possible to write portable applications that search directories by restricting all directory access to use these new interface routines. The sources supplied here are a total rewrite of Berkeley's code, incorporating ideas from a variety of sources and conforming as closely to published standards as possible, and are in the PUBLIC DOMAIN to encourage their widespread adoption. They support access to Apple ProDOS directories by exploiting an emulation of the SVR3 getdents() system call, which attains commonality with the generic UNIX implementation at the cost of slightly more overhead than absolutely necessary. These routines should be added to the standard (UNIX-compatible) C library on all Apple II systems, and all existing and future applications should be changed to use this interface. Once this is done (and similar effort expended in the UNIX community), there should be no portability problems between UNIX and ProDOS due to differences in underlying directory structures. (When porting your applications to UNIX systems, you can always carry the corresponding UNIX package around with you.) An additional benefit of these routines is that they buffer directory input, which provides improved access speed over raw read()s of one entry at a time. One annoying compatibility problem has arisen along the way, namely that the original Berkeley interface used the same name, struct direct, for the new data structure as had been used for the original UNIX filesystem directory record structure. This name was changed by the IEEE 1003.1 (POSIX) Working Group to "struct dirent" and was picked up for SVR3 under the new name; it is also the name used in this portable package. I believe it is necessary to bite the bullet and adopt the new non-conflicting name. Code using a 4.2BSD- compatible package needs to be slightly revised to work with this new package, as follows: Change #include <ndir.h> /* Ninth Edition UNIX */ or #include <sys/dir.h> /* 4.2BSD */ or #include <dir.h> /* BRL System V emulation */ to #include <sys/types.h> /* if not already #included */ #include <dirent.h> Change struct direct to struct dirent Change (anything)->d_namlen to strlen( (anything)->d_name ) There is a minor compatibility problem in that the closedir() function was originally defined to have type void, but IEEE 1003.1 changed this to type int, which is what this implementation supports (even though I disagree with the change). However, the difference does not affect most applications. Another minor problem is that IEEE 1003.1 defined the d_name member of a struct dirent to be an array of maximum length; this does not permit use of compact variable-length entries directly from a directory block buffer. This part of the specification is incompatible with efficient use of the getdents() system call, and I have therefore chosen to follow the SVID specification instead of IEEE 1003.1 (which I hope is changed for the final-use standard). This deviation should have little or no impact on sensibly-coded applications, since the relevant d_name length is that given by strlen(), not the declared array size. Error handling is not completely satisfactory, due to the variety of possible failure modes in a general setting. For example, the rewinddir() function might fail, but there is no good way to indicate this. I have tried to follow the specifications in IEEE 1003.1 and the SVID as closely as possible, but there are minor deviations in this regard. Applications should not rely too heavily on exact failure mode semantics. Please do not change the new standard interface in any way, as that would defeat the major purpose of this package! (It's okay to alter the internal implementation if you really have to.) Installation instructions can be found in the file named "install". This implementation is provided by: Douglas A. Gwyn U.S. Army Ballistic Research Laboratory SLCBR-VL-V Aberdeen Proving Ground, MD 21005-5066 (301)278-6647 Gwyn@BRL.MIL This is UNSUPPORTED, use-at-your-own-risk, free software in the public domain. However, I would appreciate hearing of any actual bugs you find in this implementation and/or any improvements you come up with. dirent.h /* <dirent.h> -- definitions for SVR3 directory access routines (Aztec C65 ProDOS version) last edit: 23-May-1987 D A Gwyn Prerequisite: <sys/types.h> */ #include <sys/dirent.h> #define DIRBUF 364 /* buffer size for fs-indep. dirs */ typedef struct { int dd_fd; /* file descriptor */ int dd_loc; /* offset in block */ int dd_size; /* amount of valid data */ char *dd_buf; /* -> directory block */ } DIR; /* stream data from opendir() */ extern DIR *opendir(); extern struct dirent *readdir(); extern off_t telldir(); extern void seekdir(); extern void rewinddir(); extern int closedir(); #ifndef NULL #define NULL 0 /* DAG -- added for convenience */ #endif sys.dir.h /* <sys/dir.h> -- definitions for ProDOS directories last edit: 23-May-1987 D A Gwyn Reference: Apple IIGS ProDOS 16 Reference Manual A directory consists of some number of blocks of DIRBLKSIZ bytes each. Each DIRBLKSIZ-byte block contains two daddr_t block linkage pointers followed by entries_per_block file entry structures, except that the first entry in the first block is a directory header entry instead. All entries (including the header) are entry_length bytes long. There are file_count active entries in the directory; inactive entries have 0 for their storage_type/name_length byte. prerequisite: <sys/types.h> */ #define DIRBLKSIZ 512 /* size of directory block */ #define MAXNAMELEN 15 /* maximum filename length */ /* NOTE: not MAXNAMLEN, which has been preempted by SVR3 <dirent.h> */ struct direct /* directory entry data from read() */ { unsigned char d_namlen; /* storage_type << 4 | name_length */ #define NAMELENMASK 0x0F /* mask to isolate name_length */ #define STSHIFT 4 /* shift to isolate storage_type */ #define ST_SEEDLING 0x1 /* seedling file */ #define ST_SAPLING 0x2 /* sapling file */ #define ST_TREE 0x3 /* tree file */ #define ST_PASCAL 0x4 /* Pascal area */ #define ST_SUBDIR 0xD /* subdirectory */ #define ST_SUBKEY 0xE /* subdirectory key block */ #define ST_VOLKEY 0xF /* volume directory key block */ char d_name[MAXNAMELEN]; /* space-padded filename */ /* next four members valid for file entry only */ unsigned char d_filtyp; /* file_type */ daddr_t d_keyptr; /* key_pointer */ unsigned short d_blused; /* blocks_used */ unsigned char d_eof[3]; /* 3-byte EOF */ Pdate_t d_cdate; /* create_date */ Ptime_t d_ctime; /* create_time */ unsigned char d_ver; /* version */ unsigned char d_minver; /* min_version */ union { struct { /* file entry only */ unsigned char f_access; /* access */ unsigned short f_auxtyp; /* aux_type */ Pdate_t f_mdate; /* mod_date */ Ptime_t f_mtime; /* mod_time */ daddr_t f_hdrptr; /* header_pointer */ } f; struct { /* volume or subdirectory only */ unsigned char vs_access; /* access */ unsigned char vs_entlen; /* entry_length */ unsigned char vs_eperbl; /* entries_per_block */ unsigned short vs_filcnt; /* file_count */ union { struct { /* volume directory only */ daddr_t v_bitmap; /* bit_map_pointer */ unsigned short v_blocks; /* total_blocks */ } v; struct { /* subdirectory only */ daddr_t s_parptr; /* parent_pointer */ unsigned char s_parnum; /* parent_entry_number */ unsigned char s_parlen; /* parent_entry_length */ } s; } u; } vs; } u; }; sys.dirent.h /* <sys/dirent.h> -- file system independent directory entry (SVR3) (Aztec C65 ProDOS version) last edit: 23-May-1987 D A Gwyn prerequisite: <sys/types.h> */ struct dirent /* data from getdents()/readdir() */ { long d_ino; /* key block number of entry */ off_t d_off; /* offset of disk directory entry */ unsigned short d_reclen; /* length of this record */ char d_name[1]; /* name of file */ /* non-POSIX */ }; #define DIRENTBASESIZ (sizeof(long) + sizeof(off_t) \ + sizeof(unsigned short)) /* Aztec C65 */ #define DIRENTSIZ( namlen ) ((DIRENTBASESIZ + sizeof(long) + (namlen)) \ / sizeof(long) * sizeof(long)) /* DAG -- the following was moved from <dirent.h>, which was the wrong place */ #define MAXNAMLEN 128 /* maximum filename length */ #ifndef NAME_MAX #define NAME_MAX (MAXNAMLEN - 1) /* DAG -- added for POSIX */ #endif sys.types.h /* <sys/types.h> -- primitive system data types for ProDOS last edit: 22-May-1987 D A Gwyn */ typedef unsigned short daddr_t; /* disk block address */ typedef char *caddr_t; /* byte address */ typedef unsigned long time_t; /* time in seconds */ typedef long off_t; /* file position offset */ typedef unsigned short Pdate_t; /* ProDOS date format */ typedef unsigned short Ptime_t; /* ProDOS time format */ closedir.c /* closedir -- close a directory stream (Aztec C65 ProDOS version) last edit: 23-May-1987 D A Gwyn */ #include <errno.h> #include <sys/types.h> #include <dirent.h> typedef void *pointer; extern void free(); extern int close(); int closedir( dirp ) register DIR *dirp; /* stream from opendir() */ { if ( dirp == NULL || dirp->dd_buf == NULL ) { errno = EFAULT; return -1; /* invalid pointer */ } free( (pointer)dirp->dd_buf ); free( (pointer)dirp ); return close( dirp->dd_fd ); } opendir.c /* opendir -- open a directory stream (Aztec C65 ProDOS version) last edit: 24-May-1987 D A Gwyn */ #include <errno.h> #include <fcntl.h> #include <sys/types.h> #include <dirent.h> typedef void *pointer; extern void free(); extern pointer malloc(); extern int getdents(), open(), close(); DIR * opendir( dirname ) char *dirname; /* name of directory */ { register DIR *dirp; /* -> malloc'ed storage */ register int fd; /* file descriptor for read */ if ( (fd = open( dirname, O_RDONLY )) < 0 ) return NULL; /* errno set by open() */ if ( (dirp = (DIR *)malloc( sizeof(DIR) )) == NULL || (dirp->dd_buf = (char *)malloc( (unsigned)DIRBUF )) == NULL ) { if ( dirp != NULL ) free( (pointer)dirp ); (void)close( fd ); errno = ENOMEM; return NULL; /* not enough memory */ } dirp->dd_fd = fd; dirp->dd_loc = 0; /* no need to skip header */ /* Special for ProDOS: check header entry rather than fstat()ing */ if ( (dirp->dd_size = getdents( fd, dirp->dd_buf, (unsigned)DIRBUF )) <= 0 /* EOF or error */ || ((struct dirent *)dirp->dd_buf)->d_ino != -1L /* kludge */ ) { (void)closedir( dirp ); errno = ENOTDIR; return NULL; /* not a directory */ } return dirp; } readdir.c /* readdir -- read next entry from a directory stream (Aztec C65 ProDOS version) last edit: 24-May-1987 D A Gwyn */ #include <errno.h> #include <sys/types.h> #include <dirent.h> extern int getdents(); /* SVR3 system call emulation */ struct dirent * readdir( dirp ) register DIR *dirp; /* stream from opendir() */ { register struct dirent *dp; /* -> directory data */ if ( dirp == NULL || dirp->dd_buf == NULL ) { errno = EFAULT; return NULL; /* invalid pointer */ } do { if ( dirp->dd_loc >= dirp->dd_size ) /* empty or obsolete */ dirp->dd_loc = dirp->dd_size = 0; if ( dirp->dd_size == 0 /* need to refill buffer */ && (dirp->dd_size = getdents( dirp->dd_fd, dirp->dd_buf, (unsigned)DIRBUF ) ) <= 0 ) return NULL; /* EOF or error */ dp = (struct dirent *)&dirp->dd_buf[dirp->dd_loc]; dirp->dd_loc += dp->d_reclen; } while ( dp->d_ino <= 0L ); /* skip header, empty slots */ return dp; } rewinddir.c /* rewinddir -- rewind a directory stream (Aztec C65 ProDOS version) last edit: 24-May-1987 D A Gwyn This is not simply a call to seekdir(), because seekdir() will use the current buffer whenever possible and we need rewinddir() to forget about buffered data. */ #include <errno.h> #include <sys/types.h> #include <dirent.h> extern off_t lseek(); #ifndef SEEK_SET #define SEEK_SET 0 #endif void rewinddir( dirp ) register DIR *dirp; /* stream from opendir() */ { if ( dirp == NULL || dirp->dd_buf == NULL ) { errno = EFAULT; return; /* invalid pointer */ } (void)lseek( dirp->dd_fd, (off_t)0, SEEK_SET ); /* may set errno */ dirp->dd_size = getdents( dirp->dd_fd, dirp->dd_buf, (unsigned)DIRBUF ); dirp->dd_loc = 0; /* no need to skip header */ } seekdir.c /* seekdir -- reposition a directory stream (Aztec C65 ProDOS version) last edit: 24-May-1987 D A Gwyn An unsuccessful seekdir() will in general alter the current directory position; beware. */ #include <errno.h> #include <sys/types.h> #include <dirent.h> extern off_t lseek(); #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 */ } /* 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 */ } } } telldir.c /* telldir -- report directory stream position (Aztec C65 ProDOS version) last edit: 24-May-1987 D A Gwyn */ #include <errno.h> #include <sys/types.h> #include <dirent.h> extern off_t lseek(); #ifndef SEEK_CUR #define SEEK_CUR 1 #endif off_t telldir( dirp ) /* return offset of next entry */ DIR *dirp; /* stream from opendir() */ { if ( dirp == NULL || dirp->dd_buf == NULL ) { errno = EFAULT; return -1; /* invalid pointer */ } if ( dirp->dd_loc < dirp->dd_size ) /* valid index */ return ((struct dirent *)&dirp->dd_buf[dirp->dd_loc])->d_off; else /* beginning of next directory block */ return lseek( dirp->dd_fd, (off_t)0, SEEK_CUR ); } getdents.c /* getdents -- get directory entries in a file system independent format (SVR3 system call emulation) (Aztec C65 ProDOS version) last edit: 25-May-1987 D A Gwyn */ #include <errno.h> #include <sys/types.h> #include <sys/dir.h> #include <sys/dirent.h> #define RecLen( dp ) (sizeof(struct direct)) /* fixed-length entries */ extern int read(); #define GetBlock( fd, buf, n ) read( fd, buf, (unsigned)n ) extern int tolower(); extern off_t lseek(); #ifndef NULL #define NULL 0 #endif #ifndef SEEK_CUR #define SEEK_CUR 1 #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[] */ { off_t offset; /* initial directory file offset */ 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[.] */ if ( buf == NULL ) /* no alignment constraint for Aztec C65 */ { errno = EFAULT; /* invalid pointer */ return -1; } if ( (offset = lseek( fildes, (off_t)0, SEEK_CUR )) < 0 ) return -1; /* errno set by lseek() */ for ( bp = (struct dirent *)buf; bp == (struct dirent *)buf; ) { /* convert next directory block */ int size; if ( (size = GetBlock( fildes, u.dblk, DIRBLKSIZ )) <= 0 ) return size; /* EOF or error (EBADF) */ for ( dp = (struct direct *)(u.dblk + 2 * sizeof(daddr_t)); /* skip block linkage pointers */ (char *)dp < &u.dblk[size]; dp = (struct direct *)((char *)dp + RecLen( dp )) ) { if ( dp->d_namlen != 0 ) { /* non-empty; copy to user buffer */ register int reclen = DIRENTSIZ( dp->d_namlen & NAMELENMASK ); if ( (char *)bp + reclen > &buf[nbyte] ) { errno = EINVAL; return -1; /* buf too small */ } switch ( dp->d_namlen >> STSHIFT ) { case ST_VOLKEY: case ST_SUBKEY: bp->d_ino = -1L; /* kludge */ break; default: bp->d_ino = dp->d_keyptr; break; } bp->d_off = offset + ((char *)dp - u.dblk); bp->d_reclen = reclen; /* translate filename to lower case */ { register char *bnp, *dnp; register int i; bnp = bp->d_name; dnp = dp->d_name; for ( i = 0; i < dp->d_namlen; ++i ) *bnp++ = tolower( *dnp++ ); for ( i += DIRENTBASESIZ; i < reclen; ++i ) /* NUL padding */ *bnp++ = '\0'; } bp = (struct dirent *)((char *)bp + reclen); } } } return (char *)bp - buf; /* return # bytes read */ } testdir.c /* testdir -- basic test for C library directory access routines last edit: 25-Apr-1987 D A Gwyn */ #include <sys/types.h> #include <stdio.h> #include <dirent.h> extern void exit(); extern int strcmp(); main( argc, argv ) int argc; register char **argv; { register DIR *dirp; register struct dirent *dp; int nerrs = 0; /* total not found */ if ( (dirp = opendir( "." )) == NULL ) { (void)fprintf( stderr, "Cannot open \".\" directory\n" ); exit( 1 ); } while ( --argc > 0 ) { ++argv; while ( (dp = readdir( dirp )) != NULL ) if ( strcmp( dp->d_name, *argv ) == 0 ) { (void)printf( "\"%s\" found.\n", *argv ); break; } if ( dp == NULL ) { (void)printf( "\"%s\" not found.\n", *argv ); ++nerrs; } rewinddir( dirp ); } (void)closedir( dirp ); exit( nerrs ); } listdir.c /* listdir -- basic test for C library directory access routines last edit: 22-May-1987 D A Gwyn */ #include <sys/types.h> #include <stdio.h> #include <dirent.h> extern void exit(); extern int strcmp(); main( argc, argv ) int argc; register char **argv; { register DIR *dirp; register struct dirent *dp; int nerrs = 0; /* total not found */ if ( (dirp = opendir( "." )) == NULL ) { (void)fprintf( stderr, "Cannot open \".\" directory\n" ); exit( 1 ); } while ( (dp = readdir( dirp )) != NULL ) (void)printf( "%s\n", dp->d_name ); (void)closedir( dirp ); exit( nerrs ); }