f5ed34
commit c00b984fcd53f679ca2dafcd1aee2c89836e6e73
f5ed34
Author: Florian Weimer <fweimer@redhat.com>
f5ed34
Date:   Tue Aug 29 08:28:31 2023 +0200
f5ed34
f5ed34
    nscd: Skip unusable entries in first pass in prune_cache (bug 30800)
f5ed34
    
f5ed34
    Previously, if an entry was marked unusable for any reason, but had
f5ed34
    not timed out yet, the assert would trigger.
f5ed34
    
f5ed34
    One way to get into such state is if a data change is detected during
f5ed34
    re-validation of an entry.  This causes the entry to be marked as not
f5ed34
    usable.  If exits nscd soon after that, then the clock jumps
f5ed34
    backwards, and nscd restarted, the cache re-validation run after
f5ed34
    startup triggers the removed assert.
f5ed34
    
f5ed34
    The change is more complicated than just the removal of the assert
f5ed34
    because entries marked as not usable should be garbage-collected in
f5ed34
    the second pass.  To make this happen, it is necessary to update some
f5ed34
    book-keeping data.
f5ed34
    
f5ed34
    Reviewed-by: DJ Delorie <dj@redhat.com>
f5ed34
f5ed34
diff --git a/nscd/cache.c b/nscd/cache.c
f5ed34
index efe4214d953edb30..2fd3f78ebb567bbe 100644
f5ed34
--- a/nscd/cache.c
f5ed34
+++ b/nscd/cache.c
f5ed34
@@ -371,8 +371,11 @@ prune_cache (struct database_dyn *table, time_t now, int fd)
f5ed34
 		       serv2str[runp->type], str, dh->timeout);
f5ed34
 	    }
f5ed34
 
f5ed34
-	  /* Check whether the entry timed out.  */
f5ed34
-	  if (dh->timeout < now)
f5ed34
+	  /* Check whether the entry timed out.  Timed out entries
f5ed34
+	     will be revalidated.  For unusable records, it is still
f5ed34
+	     necessary to record that the bucket needs to be scanned
f5ed34
+	     again below.  */
f5ed34
+	  if (dh->timeout < now || !dh->usable)
f5ed34
 	    {
f5ed34
 	      /* This hash bucket could contain entries which need to
f5ed34
 		 be looked at.  */
f5ed34
@@ -384,7 +387,7 @@ prune_cache (struct database_dyn *table, time_t now, int fd)
f5ed34
 	      /* We only have to look at the data of the first entries
f5ed34
 		 since the count information is kept in the data part
f5ed34
 		 which is shared.  */
f5ed34
-	      if (runp->first)
f5ed34
+	      if (runp->first && dh->usable)
f5ed34
 		{
f5ed34
 
f5ed34
 		  /* At this point there are two choices: we reload the
f5ed34
@@ -400,9 +403,6 @@ prune_cache (struct database_dyn *table, time_t now, int fd)
f5ed34
 		    {
f5ed34
 		      /* Remove the value.  */
f5ed34
 		      dh->usable = false;
f5ed34
-
f5ed34
-		      /* We definitely have some garbage entries now.  */
f5ed34
-		      any = true;
f5ed34
 		    }
f5ed34
 		  else
f5ed34
 		    {
f5ed34
@@ -414,18 +414,15 @@ prune_cache (struct database_dyn *table, time_t now, int fd)
f5ed34
 
f5ed34
 		      time_t timeout = readdfcts[runp->type] (table, runp, dh);
f5ed34
 		      next_timeout = MIN (next_timeout, timeout);
f5ed34
-
f5ed34
-		      /* If the entry has been replaced, we might need
f5ed34
-			 cleanup.  */
f5ed34
-		      any |= !dh->usable;
f5ed34
 		    }
f5ed34
 		}
f5ed34
+
f5ed34
+	      /* If the entry has been replaced, we might need cleanup.  */
f5ed34
+	      any |= !dh->usable;
f5ed34
 	    }
f5ed34
 	  else
f5ed34
-	    {
f5ed34
-	      assert (dh->usable);
f5ed34
-	      next_timeout = MIN (next_timeout, dh->timeout);
f5ed34
-	    }
f5ed34
+	    /* Entry has not timed out and is usable.  */
f5ed34
+	    next_timeout = MIN (next_timeout, dh->timeout);
f5ed34
 
f5ed34
 	  run = runp->next;
f5ed34
 	}