diff options
Diffstat (limited to 'lib/libc/gen')
-rw-r--r-- | lib/libc/gen/directory.3 | 25 | ||||
-rw-r--r-- | lib/libc/gen/readdir.c | 6 | ||||
-rw-r--r-- | lib/libc/gen/rewinddir.c | 1 | ||||
-rw-r--r-- | lib/libc/gen/telldir.c | 37 | ||||
-rw-r--r-- | lib/libc/gen/telldir.h | 1 |
5 files changed, 69 insertions, 1 deletions
diff --git a/lib/libc/gen/directory.3 b/lib/libc/gen/directory.3 index 1e864f8..e1874da 100644 --- a/lib/libc/gen/directory.3 +++ b/lib/libc/gen/directory.3 @@ -267,4 +267,27 @@ The invalidation of .Fn telldir tokens when calling .Fn seekdir -is non-standard. +is non-standard. This is a compile time option. +.Pp +The behaviour of +.Fn telldir +and +.Fn seekdir +is likely to be wrong if there are parallel unlinks happening +and the directory is larger than one page. +There is code to ensure that a +.Fn seekdir +to the location given by a +.Fn telldir +immediately before the last +.Fn readdir +will always set the correct location to return the same value as that last +.Fn readdir +performed. +This is enough for some applications which want to "push back the last entry read" E.g. Samba. +Seeks back to any other location, +other than the beginning of the directory, +may result in unexpected behaviour if deletes are present. +It is hoped that this situation will be resolved with changes to +.Fn getdirentries +and the VFS. diff --git a/lib/libc/gen/readdir.c b/lib/libc/gen/readdir.c index 69f59d1..c6f5d3f 100644 --- a/lib/libc/gen/readdir.c +++ b/lib/libc/gen/readdir.c @@ -54,19 +54,25 @@ _readdir_unlocked(dirp, skip) int skip; { struct dirent *dp; + long initial_seek; + long initial_loc = 0; for (;;) { if (dirp->dd_loc >= dirp->dd_size) { if (dirp->dd_flags & __DTF_READALL) return (NULL); + initial_loc = dirp->dd_loc; + dirp->dd_flags &= ~__DTF_SKIPREAD; dirp->dd_loc = 0; } if (dirp->dd_loc == 0 && !(dirp->dd_flags & (__DTF_READALL | __DTF_SKIPREAD))) { + initial_seek = dirp->dd_seek; dirp->dd_size = _getdirentries(dirp->dd_fd, dirp->dd_buf, dirp->dd_len, &dirp->dd_seek); if (dirp->dd_size <= 0) return (NULL); + _fixtelldir(dirp, initial_seek, initial_loc); } dirp->dd_flags &= ~__DTF_SKIPREAD; dp = (struct dirent *)(dirp->dd_buf + dirp->dd_loc); diff --git a/lib/libc/gen/rewinddir.c b/lib/libc/gen/rewinddir.c index 193f4b0..e157cd6 100644 --- a/lib/libc/gen/rewinddir.c +++ b/lib/libc/gen/rewinddir.c @@ -51,6 +51,7 @@ rewinddir(dirp) if (__isthreaded) _pthread_mutex_lock(&dirp->dd_lock); + dirp->dd_flags &= ~__DTF_SKIPREAD; /* current contents are invalid */ if (dirp->dd_flags & __DTF_READALL) _filldir(dirp, false); else { diff --git a/lib/libc/gen/telldir.c b/lib/libc/gen/telldir.c index d72b500..19cd6ee 100644 --- a/lib/libc/gen/telldir.c +++ b/lib/libc/gen/telldir.c @@ -101,9 +101,22 @@ _seekdir(dirp, loc) return; if (lp->loc_loc == dirp->dd_loc && lp->loc_seek == dirp->dd_seek) return; + + /* If it's within the same chunk of data, don't bother reloading. */ + if (lp->loc_seek == dirp->dd_seek) { + /* + * If we go back to 0 don't make the next readdir + * trigger a call to getdirentries(). + */ + if (lp->loc_loc == 0) + dirp->dd_flags |= __DTF_SKIPREAD; + dirp->dd_loc = lp->loc_loc; + return; + } (void) lseek(dirp->dd_fd, (off_t)lp->loc_seek, SEEK_SET); dirp->dd_seek = lp->loc_seek; dirp->dd_loc = 0; + dirp->dd_flags &= ~__DTF_SKIPREAD; /* current contents are invalid */ while (dirp->dd_loc < lp->loc_loc) { dp = _readdir_unlocked(dirp, 0); if (dp == NULL) @@ -112,6 +125,30 @@ _seekdir(dirp, loc) } /* + * After readdir returns the last entry in a block, a call to telldir + * returns a location that is after the end of that last entry. + * However, that location doesn't refer to a valid directory entry. + * Ideally, the call to telldir would return a location that refers to + * the first entry in the next block. That location is not known + * until the next block is read, so readdir calls this function after + * fetching a new block to fix any such telldir locations. + */ +void +_fixtelldir(DIR *dirp, long oldseek, long oldloc) +{ + struct ddloc *lp; + + lp = LIST_FIRST(&dirp->dd_td->td_locq); + if (lp != NULL) { + if (lp->loc_loc == oldloc && + lp->loc_seek == oldseek) { + lp->loc_seek = dirp->dd_seek; + lp->loc_loc = dirp->dd_loc; + } + } +} + +/* * Reclaim memory for telldir cookies which weren't used. */ void diff --git a/lib/libc/gen/telldir.h b/lib/libc/gen/telldir.h index 04989bb..bccabb1 100644 --- a/lib/libc/gen/telldir.h +++ b/lib/libc/gen/telldir.h @@ -64,5 +64,6 @@ bool _filldir(DIR *, bool); struct dirent *_readdir_unlocked(DIR *, int); void _reclaim_telldir(DIR *); void _seekdir(DIR *, long); +void _fixtelldir(DIR *dirp, long oldseek, long oldloc); #endif |