There is a bug in all seekdir()/readdir() implementations in all BSDs:

Under some circumstance, a seekdir() will no take you to the expected
position, but one further.  This happens when the first entry in a
block has been unlinked and you seekdir to the second.  In this case
seekdir() overshoots by one entry.

_readdir_unlocked() must not skip deleted entries when being called
from _seekdir().

A diff is attached.

- Marc Balmer
--- readdir.c.orig      Fri May  2 10:25:47 2008
+++ readdir.c   Fri May  2 10:27:18 2008
@@ -51,7 +51,7 @@
  * get next entry in a directory.
  */
 struct dirent *
-_readdir_unlocked(DIR *dirp)
+_readdir_unlocked(DIR *dirp, int skipdeleted)
 {
        struct dirent *dp;
        long dummy;
@@ -73,7 +73,7 @@
                if (_DIRENT_DIRSIZ(dp) > dirp->dd_len + 1 - dirp->dd_loc)
                        return (NULL);
                dirp->dd_loc += _DIRENT_DIRSIZ(dp);
-               if (dp->d_ino == 0)
+               if (dp->d_ino == 0 && skipdeleted)
                        continue;
                if (dp->d_type == DT_WHT && (dirp->dd_flags & DTF_HIDEW))
                        continue;
@@ -88,7 +88,7 @@
 
        if (__isthreaded)
                _pthread_mutex_lock((pthread_mutex_t *)&dirp->dd_lock);
-       dp = _readdir_unlocked(dirp);
+       dp = _readdir_unlocked(dirp, 0);
        if (__isthreaded)
                _pthread_mutex_unlock((pthread_mutex_t *)&dirp->dd_lock);
 
@@ -105,10 +105,10 @@
        errno = 0;
        if (__isthreaded)
                _pthread_mutex_lock((pthread_mutex_t *)&dirp->dd_lock);
-       if ((dp = _readdir_unlocked(dirp)) != NULL)
+       if ((dp = _readdir_unlocked(dirp, 0)) != NULL)
                memcpy(entry, dp, _DIRENT_MINSIZ(dp));
        if (__isthreaded)
-               _pthread_mutex_unlock((pthread_mutex_t *)&dirp->dd_lock);
+               _pthread_mutex_unlock((pthread_mutex_t *)&dirp->dd_lock, 0);
 
        if (errno != 0) {
                if (dp == NULL) {
--- telldir.c.orig      Fri May  2 10:25:58 2008
+++ telldir.c   Fri May  2 10:26:02 2008
@@ -148,7 +148,7 @@
         * load a new buffer or for dd_loc to not match directly.
         */
        while (dirp->dd_loc < lp->loc_loc && dirp->dd_seek == lp->loc_seek) {
-               dp = _readdir_unlocked(dirp);
+               dp = _readdir_unlocked(dirp, 1);
                if (dp == NULL)
                        break;
        }

Reply via email to