Tweak seekdir, telldir and readdir so that when htere are deletes going on,

as seek to teh last location saved will still work. This is needed for Samba
to be able to correctly handle delete requests from windows. This does not
completely fix seekdir when deletes are present but fixes the worst of the
problems. The real solution must involve some changes to the API for eh VFS
and getdirentries(2).

Obtained from:	Panzura inc
MFC after:	1 week
This commit is contained in:
julian 2015-05-05 14:52:33 +00:00
parent 86c09d04c8
commit 7e3f691222
5 changed files with 65 additions and 1 deletions

View File

@ -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.

View File

@ -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);

View File

@ -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 {

View File

@ -101,9 +101,21 @@ _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)
@ -111,6 +123,27 @@ _seekdir(dirp, loc)
}
}
/*
* when we do a read and cross a boundary, any telldir we
* just did will have wrong information in it.
* We need to move it from "beyond the end of the previous chunk"
* to "the beginning of the new chunk"
*/
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.
*/

View File

@ -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