ack/lang/cem/libcc.ansi/misc/seekdir.c
2018-06-21 22:33:47 +02:00

108 lines
2.8 KiB
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 <errno.h>
#include <sys/errno.h>
#include <sys/types.h>
#include <dirent.h>
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 */
}
}
}