Pristine Ack-5.5
[Ack-5.5.git] / lang / cem / libcc.ansi / misc / seekdir.c
1 /*
2         seekdir -- reposition a directory stream
3
4         last edit:      24-May-1987     D A Gwyn
5
6         An unsuccessful seekdir() will in general alter the current
7         directory position; beware.
8
9         NOTE:   4.nBSD directory compaction makes seekdir() & telldir()
10                 practically impossible to do right.  Avoid using them!
11 */
12
13 #include        <errno.h>
14 #include        <sys/errno.h>
15 #include        <sys/types.h>
16 #include        <dirent.h>
17
18 extern off_t _lseek(int d, int offset, int whence);
19
20 #ifndef NULL
21 #define NULL    0
22 #endif
23
24 #ifndef SEEK_SET
25 #define SEEK_SET        0
26 #endif
27
28 typedef int     bool;                   /* Boolean data type */
29 #define false   0
30 #define true    1
31
32 void
33 seekdir(register DIR *dirp, register off_t loc)
34 /* loc == position from telldir() */
35 {
36         register bool   rewind;         /* "start over when stymied" flag */
37
38         if ( dirp == NULL || dirp->dd_buf == NULL )
39                 {
40                 errno = EFAULT;
41                 return;                 /* invalid pointer */
42                 }
43
44         /* A (struct dirent)'s d_off is an invented quantity on 4.nBSD
45            NFS-supporting systems, so it is not safe to lseek() to it. */
46
47         /* Monotonicity of d_off is heavily exploited in the following. */
48
49         /* This algorithm is tuned for modest directory sizes.  For
50            huge directories, it might be more efficient to read blocks
51            until the first d_off is too large, then back up one block,
52            or even to use binary search on the directory blocks.  I
53            doubt that the extra code for that would be worthwhile. */
54
55         if ( dirp->dd_loc >= dirp->dd_size      /* invalid index */
56           || ((struct dirent *)&dirp->dd_buf[dirp->dd_loc])->d_off > loc
57                                         /* too far along in buffer */
58            )
59                 dirp->dd_loc = 0;       /* reset to beginning of buffer */
60         /* else save time by starting at current dirp->dd_loc */
61
62         for ( rewind = true; ; )
63                 {
64                 register struct dirent  *dp;
65
66                 /* See whether the matching entry is in the current buffer. */
67
68                 if ( (dirp->dd_loc < dirp->dd_size      /* valid index */
69                    || readdir( dirp ) != NULL   /* next buffer read */
70                    && (dirp->dd_loc = 0, true)  /* beginning of buffer set */
71                      )
72                   && (dp = (struct dirent *)&dirp->dd_buf[dirp->dd_loc])->d_off
73                         <= loc          /* match possible in this buffer */
74                    )    {
75                         for ( /* dp initialized above */ ;
76                               (char *)dp < &dirp->dd_buf[dirp->dd_size];
77                               dp = (struct dirent *)((char *)dp + dp->d_reclen)
78                             )
79                                 if ( dp->d_off == loc )
80                                         {       /* found it! */
81                                         dirp->dd_loc =
82                                                 (char *)dp - dirp->dd_buf;
83                                         return;
84                                         }
85
86                         rewind = false; /* no point in backing up later */
87                         dirp->dd_loc = dirp->dd_size;   /* set end of buffer */
88                         }
89                 else                    /* whole buffer past matching entry */
90                         if ( !rewind )
91                                 {       /* no point in searching further */
92                                 errno = EINVAL;
93                                 return; /* no entry at specified loc */
94                                 }
95                         else    {       /* rewind directory and start over */
96                                 rewind = false; /* but only once! */
97
98                                 dirp->dd_loc = dirp->dd_size = 0;
99
100                                 if ( _lseek( dirp->dd_fd, (off_t)0, SEEK_SET )
101                                         != 0
102                                    )
103                                         return; /* errno already set (EBADF) */
104
105                                 if ( loc == 0 )
106                                         return; /* save time */
107                                 }
108                 }
109 }