/* 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 #include #include #include extern off_t _lseek(int d, int offset, int whence); #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(register DIR* dirp, register off_t loc) /* 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 */ } } }