When replacing cache entries, preserve CNAMES which target them.

When inserting new cache records, we first delete existing records
of the same name/type, to maintain consistency. This has the side effect
of deleting any CNAMES which have the records as target. So

cname1.example CNAME record.example
cname2.example CNAME record.example

looking up cname2.example will push it into the cache, and also
push record.example. Doing that deletes any cache of cname1.example.

This changeset avoids that problem by making sure that when
deleting record.example, and re-insterting it (with the same name -important),
it uses the same struct crec, with the same uid. This preserves the existing cnames.
diff --git a/src/cache.c b/src/cache.c
index b4dcd0e..bdb998e 100644
--- a/src/cache.c
+++ b/src/cache.c
@@ -325,7 +325,8 @@
   return 1;
 }
 
-static struct crec *cache_scan_free(char *name, struct all_addr *addr, time_t now, unsigned short flags)
+static struct crec *cache_scan_free(char *name, struct all_addr *addr, time_t now, unsigned short flags,
+				    struct crec **target_crec, unsigned int *target_uid)
 {
   /* Scan and remove old entries.
      If (flags & F_FORWARD) then remove any forward entries for name and any expired
@@ -338,7 +339,10 @@
      to a cache entry if the name exists in the cache as a HOSTS or DHCP entry (these are never deleted)
 
      We take advantage of the fact that hash chains have stuff in the order <reverse>,<other>,<immortal>
-     so that when we hit an entry which isn't reverse and is immortal, we're done. */
+     so that when we hit an entry which isn't reverse and is immortal, we're done. 
+
+     If we free a crec which is a CNAME target, return the entry and uid in target_crec and target_uid.
+     This entry will get re-used with the same name, to preserve CNAMEs. */
  
   struct crec *crecp, **up;
   
@@ -346,17 +350,6 @@
     {
       for (up = hash_bucket(name), crecp = *up; crecp; crecp = crecp->hash_next)
 	{
-	  if (is_expired(now, crecp) || is_outdated_cname_pointer(crecp))
-	    { 
-	      *up = crecp->hash_next;
-	      if (!(crecp->flags & (F_HOSTS | F_DHCP | F_CONFIG)))
-		{
-		  cache_unlink(crecp);
-		  cache_free(crecp);
-		}
-	      continue;
-	    } 
-	
 	  if ((crecp->flags & F_FORWARD) && hostname_isequal(cache_get_name(crecp), name))
 	    {
 	      /* Don't delete DNSSEC in favour of a CNAME, they can co-exist */
@@ -366,6 +359,16 @@
 		  if (crecp->flags & (F_HOSTS | F_DHCP | F_CONFIG))
 		    return crecp;
 		  *up = crecp->hash_next;
+		  /* If this record is for the name we're inserting and is the target
+		     of a CNAME record. Make the new record for the same name, in the same
+		     crec, with the same uid to avoid breaking the existing CNAME. */
+		  if (crecp->uid != UID_NONE)
+		    {
+		      if (target_crec)
+			*target_crec = crecp;
+		      if (target_uid)
+			*target_uid = crecp->uid;
+		    }
 		  cache_unlink(crecp);
 		  cache_free(crecp);
 		  continue;
@@ -384,6 +387,18 @@
 		}
 #endif
 	    }
+
+	  if (is_expired(now, crecp) || is_outdated_cname_pointer(crecp))
+	    { 
+	      *up = crecp->hash_next;
+	      if (!(crecp->flags & (F_HOSTS | F_DHCP | F_CONFIG)))
+		{
+		  cache_unlink(crecp);
+		  cache_free(crecp);
+		}
+	      continue;
+	    } 
+	  
 	  up = &crecp->hash_next;
 	}
     }
@@ -450,11 +465,12 @@
 struct crec *cache_insert(char *name, struct all_addr *addr, 
 			  time_t now,  unsigned long ttl, unsigned short flags)
 {
-  struct crec *new;
+  struct crec *new, *target_crec = NULL;
   union bigname *big_name = NULL;
   int freed_all = flags & F_REVERSE;
   int free_avail = 0;
-
+  unsigned int target_uid;
+  
   /* Don't log DNSSEC records here, done elsewhere */
   if (flags & (F_IPV4 | F_IPV6 | F_CNAME))
     {
@@ -472,7 +488,7 @@
   
   /* First remove any expired entries and entries for the name/address we
      are currently inserting. */
-  if ((new = cache_scan_free(name, addr, now, flags)))
+  if ((new = cache_scan_free(name, addr, now, flags, &target_crec, &target_uid)))
     {
       /* We're trying to insert a record over one from 
 	 /etc/hosts or DHCP, or other config. If the 
@@ -496,81 +512,89 @@
     }
   
   /* Now get a cache entry from the end of the LRU list */
-  while (1) {
-    if (!(new = cache_tail)) /* no entries left - cache is too small, bail */
-      {
-	insert_error = 1;
-	return NULL;
-      }
-    
-    /* End of LRU list is still in use: if we didn't scan all the hash
-       chains for expired entries do that now. If we already tried that
-       then it's time to start spilling things. */
-    
-    if (new->flags & (F_FORWARD | F_REVERSE))
-      { 
-	/* If free_avail set, we believe that an entry has been freed.
-	   Bugs have been known to make this not true, resulting in
-	   a tight loop here. If that happens, abandon the
-	   insert. Once in this state, all inserts will probably fail. */
-	if (free_avail)
-	  {
-	    static int warned = 0;
-	    if (!warned)
-	      {
-		my_syslog(LOG_ERR, _("Internal error in cache."));
-		warned = 1;
-	      }
-	    insert_error = 1;
-	    return NULL;
-	  }
-		
-	if (freed_all)
-	  {
-	    struct all_addr free_addr = new->addr.addr;;
+  if (!target_crec)
+    while (1) {
+      if (!(new = cache_tail)) /* no entries left - cache is too small, bail */
+	{
+	  insert_error = 1;
+	  return NULL;
+	}
+      
+      /* Free entry at end of LRU list, use it. */
+      if (!(new->flags & (F_FORWARD | F_REVERSE)))
+	break;
 
+      /* End of LRU list is still in use: if we didn't scan all the hash
+	 chains for expired entries do that now. If we already tried that
+	 then it's time to start spilling things. */
+      
+      /* If free_avail set, we believe that an entry has been freed.
+	 Bugs have been known to make this not true, resulting in
+	 a tight loop here. If that happens, abandon the
+	 insert. Once in this state, all inserts will probably fail. */
+      if (free_avail)
+	{
+	  static int warned = 0;
+	  if (!warned)
+	    {
+	      my_syslog(LOG_ERR, _("Internal error in cache."));
+	      warned = 1;
+	    }
+	  insert_error = 1;
+	  return NULL;
+	}
+      
+      if (freed_all)
+	{
+	  struct all_addr free_addr = new->addr.addr;;
+	  
 #ifdef HAVE_DNSSEC
-	    /* For DNSSEC records, addr holds class. */
-	    if (new->flags & (F_DS | F_DNSKEY))
-	      free_addr.addr.dnssec.class = new->uid;
+	  /* For DNSSEC records, addr holds class. */
+	  if (new->flags & (F_DS | F_DNSKEY))
+	    free_addr.addr.dnssec.class = new->uid;
 #endif
-	    
-	    free_avail = 1; /* Must be free space now. */
-	    cache_scan_free(cache_get_name(new), &free_addr, now, new->flags);
-	    cache_live_freed++;
-	  }
-	else
-	  {
-	    cache_scan_free(NULL, NULL, now, 0);
-	    freed_all = 1;
-	  }
-	continue;
-      }
- 
-    /* Check if we need to and can allocate extra memory for a long name.
-       If that fails, give up now, always succeed for DNSSEC records. */
-    if (name && (strlen(name) > SMALLDNAME-1))
-      {
-	if (big_free)
-	  { 
-	    big_name = big_free;
-	    big_free = big_free->next;
-	  }
-	else if ((bignames_left == 0 && !(flags & (F_DS | F_DNSKEY))) ||
-		 !(big_name = (union bigname *)whine_malloc(sizeof(union bigname))))
-	  {
-	    insert_error = 1;
-	    return NULL;
-	  }
-	else if (bignames_left != 0)
-	  bignames_left--;
-	
-      }
+	  
+	  free_avail = 1; /* Must be free space now. */
+	  cache_scan_free(cache_get_name(new), &free_addr, now, new->flags, NULL, NULL);
+	  cache_live_freed++;
+	}
+      else
+	{
+	  cache_scan_free(NULL, NULL, now, 0, NULL, NULL);
+	  freed_all = 1;
+	}
+    }
+      
+  /* Check if we need to and can allocate extra memory for a long name.
+     If that fails, give up now, always succeed for DNSSEC records. */
+  if (name && (strlen(name) > SMALLDNAME-1))
+    {
+      if (big_free)
+	{ 
+	  big_name = big_free;
+	  big_free = big_free->next;
+	}
+      else if ((bignames_left == 0 && !(flags & (F_DS | F_DNSKEY))) ||
+	       !(big_name = (union bigname *)whine_malloc(sizeof(union bigname))))
+	{
+	  insert_error = 1;
+	  return NULL;
+	}
+      else if (bignames_left != 0)
+	bignames_left--;
+      
+    }
 
-    /* Got the rest: finally grab entry. */
-    cache_unlink(new);
-    break;
-  }
+  /* If we freed a cache entry for our name which was a CNAME target, use that.
+     and preserve the uid, so that existing CNAMES are not broken. */
+  if (target_crec)
+    {
+      new = target_crec;
+      new->uid = target_uid;
+    }
+  
+  /* Got the rest: finally grab entry. */
+  cache_unlink(new);
   
   new->flags = flags;
   if (big_name)
@@ -1248,7 +1272,7 @@
 	}
       else if (!(crec->flags & F_DHCP))
 	{
-	  cache_scan_free(host_name, NULL, 0, crec->flags & (flags | F_CNAME | F_FORWARD));
+	  cache_scan_free(host_name, NULL, 0, crec->flags & (flags | F_CNAME | F_FORWARD), NULL, NULL);
 	  /* scan_free deletes all addresses associated with name */
 	  break;
 	}
@@ -1275,7 +1299,7 @@
       if (crec->flags & F_NEG)
 	{
 	  flags |= F_REVERSE;
-	  cache_scan_free(NULL, (struct all_addr *)host_address, 0, flags);
+	  cache_scan_free(NULL, (struct all_addr *)host_address, 0, flags, NULL, NULL);
 	}
     }
   else