about summary refs log tree commit diff
path: root/REORG.TODO/nscd/cache.c
diff options
context:
space:
mode:
Diffstat (limited to 'REORG.TODO/nscd/cache.c')
-rw-r--r--REORG.TODO/nscd/cache.c540
1 files changed, 540 insertions, 0 deletions
diff --git a/REORG.TODO/nscd/cache.c b/REORG.TODO/nscd/cache.c
new file mode 100644
index 0000000000..b9dbc7a0bd
--- /dev/null
+++ b/REORG.TODO/nscd/cache.c
@@ -0,0 +1,540 @@
+/* Copyright (c) 1998-2017 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+   Contributed by Ulrich Drepper <drepper@cygnus.com>, 1998.
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published
+   by the Free Software Foundation; version 2 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, see <http://www.gnu.org/licenses/>.  */
+
+#include <assert.h>
+#include <atomic.h>
+#include <errno.h>
+#include <error.h>
+#include <inttypes.h>
+#include <limits.h>
+#include <stdlib.h>
+#include <string.h>
+#include <libintl.h>
+#include <arpa/inet.h>
+#include <rpcsvc/nis.h>
+#include <sys/mman.h>
+#include <sys/param.h>
+#include <sys/stat.h>
+#include <sys/uio.h>
+
+#include "nscd.h"
+#include "dbg_log.h"
+
+
+/* Wrapper functions with error checking for standard functions.  */
+extern void *xcalloc (size_t n, size_t s);
+
+
+/* Number of times a value is reloaded without being used.  UINT_MAX
+   means unlimited.  */
+unsigned int reload_count = DEFAULT_RELOAD_LIMIT;
+
+
+static time_t (*const readdfcts[LASTREQ]) (struct database_dyn *,
+					   struct hashentry *,
+					   struct datahead *) =
+{
+  [GETPWBYNAME] = readdpwbyname,
+  [GETPWBYUID] = readdpwbyuid,
+  [GETGRBYNAME] = readdgrbyname,
+  [GETGRBYGID] = readdgrbygid,
+  [GETHOSTBYNAME] = readdhstbyname,
+  [GETHOSTBYNAMEv6] = readdhstbynamev6,
+  [GETHOSTBYADDR] = readdhstbyaddr,
+  [GETHOSTBYADDRv6] = readdhstbyaddrv6,
+  [GETAI] = readdhstai,
+  [INITGROUPS] = readdinitgroups,
+  [GETSERVBYNAME] = readdservbyname,
+  [GETSERVBYPORT] = readdservbyport,
+  [GETNETGRENT] = readdgetnetgrent,
+  [INNETGR] = readdinnetgr
+};
+
+
+/* Search the cache for a matching entry and return it when found.  If
+   this fails search the negative cache and return (void *) -1 if this
+   search was successful.  Otherwise return NULL.
+
+   This function must be called with the read-lock held.  */
+struct datahead *
+cache_search (request_type type, const void *key, size_t len,
+	      struct database_dyn *table, uid_t owner)
+{
+  unsigned long int hash = __nis_hash (key, len) % table->head->module;
+
+  unsigned long int nsearched = 0;
+  struct datahead *result = NULL;
+
+  ref_t work = table->head->array[hash];
+  while (work != ENDREF)
+    {
+      ++nsearched;
+
+      struct hashentry *here = (struct hashentry *) (table->data + work);
+
+      if (type == here->type && len == here->len
+	  && memcmp (key, table->data + here->key, len) == 0
+	  && here->owner == owner)
+	{
+	  /* We found the entry.  Increment the appropriate counter.  */
+	  struct datahead *dh
+	    = (struct datahead *) (table->data + here->packet);
+
+	  /* See whether we must ignore the entry.  */
+	  if (dh->usable)
+	    {
+	      /* We do not synchronize the memory here.  The statistics
+		 data is not crucial, we synchronize only once in a while
+		 in the cleanup threads.  */
+	      if (dh->notfound)
+		++table->head->neghit;
+	      else
+		{
+		  ++table->head->poshit;
+
+		  if (dh->nreloads != 0)
+		    dh->nreloads = 0;
+		}
+
+	      result = dh;
+	      break;
+	    }
+	}
+
+      work = here->next;
+    }
+
+  if (nsearched > table->head->maxnsearched)
+    table->head->maxnsearched = nsearched;
+
+  return result;
+}
+
+/* Add a new entry to the cache.  The return value is zero if the function
+   call was successful.
+
+   This function must be called with the read-lock held.
+
+   We modify the table but we nevertheless only acquire a read-lock.
+   This is ok since we use operations which would be safe even without
+   locking, given that the `prune_cache' function never runs.  Using
+   the readlock reduces the chance of conflicts.  */
+int
+cache_add (int type, const void *key, size_t len, struct datahead *packet,
+	   bool first, struct database_dyn *table,
+	   uid_t owner, bool prune_wakeup)
+{
+  if (__glibc_unlikely (debug_level >= 2))
+    {
+      const char *str;
+      char buf[INET6_ADDRSTRLEN + 1];
+      if (type == GETHOSTBYADDR || type == GETHOSTBYADDRv6)
+	str = inet_ntop (type == GETHOSTBYADDR ? AF_INET : AF_INET6,
+			 key, buf, sizeof (buf));
+      else
+	str = key;
+
+      dbg_log (_("add new entry \"%s\" of type %s for %s to cache%s"),
+	       str, serv2str[type], dbnames[table - dbs],
+	       first ? _(" (first)") : "");
+    }
+
+  unsigned long int hash = __nis_hash (key, len) % table->head->module;
+  struct hashentry *newp;
+
+  newp = mempool_alloc (table, sizeof (struct hashentry), 0);
+  /* If we cannot allocate memory, just do not do anything.  */
+  if (newp == NULL)
+    {
+      /* If necessary mark the entry as unusable so that lookups will
+	 not use it.  */
+      if (first)
+	packet->usable = false;
+
+      return -1;
+    }
+
+  newp->type = type;
+  newp->first = first;
+  newp->len = len;
+  newp->key = (char *) key - table->data;
+  assert (newp->key + newp->len <= table->head->first_free);
+  newp->owner = owner;
+  newp->packet = (char *) packet - table->data;
+  assert ((newp->packet & BLOCK_ALIGN_M1) == 0);
+
+  /* Put the new entry in the first position.  */
+  /* TODO Review concurrency.  Use atomic_exchange_release.  */
+  newp->next = atomic_load_relaxed (&table->head->array[hash]);
+  while (!atomic_compare_exchange_weak_release (&table->head->array[hash],
+						(ref_t *) &newp->next,
+						(ref_t) ((char *) newp
+							 - table->data)));
+
+  /* Update the statistics.  */
+  if (packet->notfound)
+    ++table->head->negmiss;
+  else if (first)
+    ++table->head->posmiss;
+
+  /* We depend on this value being correct and at least as high as the
+     real number of entries.  */
+  atomic_increment (&table->head->nentries);
+
+  /* It does not matter that we are not loading the just increment
+     value, this is just for statistics.  */
+  unsigned long int nentries = table->head->nentries;
+  if (nentries > table->head->maxnentries)
+    table->head->maxnentries = nentries;
+
+  if (table->persistent)
+    // XXX async OK?
+    msync ((void *) table->head,
+	   (char *) &table->head->array[hash] - (char *) table->head
+	   + sizeof (ref_t), MS_ASYNC);
+
+  /* We do not have to worry about the pruning thread if we are
+     re-adding the data since this is done by the pruning thread.  We
+     also do not have to do anything in case this is not the first
+     time the data is entered since different data heads all have the
+     same timeout.  */
+  if (first && prune_wakeup)
+    {
+      /* Perhaps the prune thread for the table is not running in a long
+	 time.  Wake it if necessary.  */
+      pthread_mutex_lock (&table->prune_lock);
+      time_t next_wakeup = table->wakeup_time;
+      bool do_wakeup = false;
+      if (next_wakeup > packet->timeout + CACHE_PRUNE_INTERVAL)
+	{
+	  table->wakeup_time = packet->timeout;
+	  do_wakeup = true;
+	}
+      pthread_mutex_unlock (&table->prune_lock);
+      if (do_wakeup)
+	pthread_cond_signal (&table->prune_cond);
+    }
+
+  return 0;
+}
+
+/* Walk through the table and remove all entries which lifetime ended.
+
+   We have a problem here.  To actually remove the entries we must get
+   the write-lock.  But since we want to keep the time we have the
+   lock as short as possible we cannot simply acquire the lock when we
+   start looking for timedout entries.
+
+   Therefore we do it in two stages: first we look for entries which
+   must be invalidated and remember them.  Then we get the lock and
+   actually remove them.  This is complicated by the way we have to
+   free the data structures since some hash table entries share the same
+   data.  */
+time_t
+prune_cache (struct database_dyn *table, time_t now, int fd)
+{
+  size_t cnt = table->head->module;
+
+  /* If this table is not actually used don't do anything.  */
+  if (cnt == 0)
+    {
+      if (fd != -1)
+	{
+	  /* Reply to the INVALIDATE initiator.  */
+	  int32_t resp = 0;
+	  writeall (fd, &resp, sizeof (resp));
+	}
+
+      /* No need to do this again anytime soon.  */
+      return 24 * 60 * 60;
+    }
+
+  /* If we check for the modification of the underlying file we invalidate
+     the entries also in this case.  */
+  if (table->check_file && now != LONG_MAX)
+    {
+      struct traced_file *runp = table->traced_files;
+
+      while (runp != NULL)
+	{
+#ifdef HAVE_INOTIFY
+	  if (runp->inotify_descr[TRACED_FILE] == -1)
+#endif
+	    {
+	      struct stat64 st;
+
+	      if (stat64 (runp->fname, &st) < 0)
+		{
+		  /* Print a diagnostic that the traced file was missing.
+		     We must not disable tracing since the file might return
+		     shortly and we want to reload it at the next pruning.
+		     Disabling tracing here would go against the configuration
+		     as specified by the user via check-files.  */
+		  char buf[128];
+		  dbg_log (_("checking for monitored file `%s': %s"),
+			   runp->fname, strerror_r (errno, buf, sizeof (buf)));
+		}
+	      else
+		{
+		  /* This must be `!=` to catch cases where users turn the
+		     clocks back and we still want to detect any time difference
+		     in mtime.  */
+		  if (st.st_mtime != runp->mtime)
+		    {
+		      dbg_log (_("monitored file `%s` changed (mtime)"),
+			       runp->fname);
+		      /* The file changed. Invalidate all entries.  */
+		      now = LONG_MAX;
+		      runp->mtime = st.st_mtime;
+#ifdef HAVE_INOTIFY
+		      /* Attempt to install a watch on the file.  */
+		      install_watches (runp);
+#endif
+		    }
+		}
+	    }
+
+	  runp = runp->next;
+	}
+    }
+
+  /* We run through the table and find values which are not valid anymore.
+
+     Note that for the initial step, finding the entries to be removed,
+     we don't need to get any lock.  It is at all timed assured that the
+     linked lists are set up correctly and that no second thread prunes
+     the cache.  */
+  bool *mark;
+  size_t memory_needed = cnt * sizeof (bool);
+  bool mark_use_alloca;
+  if (__glibc_likely (memory_needed <= MAX_STACK_USE))
+    {
+      mark = alloca (cnt * sizeof (bool));
+      memset (mark, '\0', memory_needed);
+      mark_use_alloca = true;
+    }
+  else
+    {
+      mark = xcalloc (1, memory_needed);
+      mark_use_alloca = false;
+    }
+  size_t first = cnt + 1;
+  size_t last = 0;
+  char *const data = table->data;
+  bool any = false;
+
+  if (__glibc_unlikely (debug_level > 2))
+    dbg_log (_("pruning %s cache; time %ld"),
+	     dbnames[table - dbs], (long int) now);
+
+#define NO_TIMEOUT LONG_MAX
+  time_t next_timeout = NO_TIMEOUT;
+  do
+    {
+      ref_t run = table->head->array[--cnt];
+
+      while (run != ENDREF)
+	{
+	  struct hashentry *runp = (struct hashentry *) (data + run);
+	  struct datahead *dh = (struct datahead *) (data + runp->packet);
+
+	  /* Some debug support.  */
+	  if (__glibc_unlikely (debug_level > 2))
+	    {
+	      char buf[INET6_ADDRSTRLEN];
+	      const char *str;
+
+	      if (runp->type == GETHOSTBYADDR || runp->type == GETHOSTBYADDRv6)
+		{
+		  inet_ntop (runp->type == GETHOSTBYADDR ? AF_INET : AF_INET6,
+			     data + runp->key, buf, sizeof (buf));
+		  str = buf;
+		}
+	      else
+		str = data + runp->key;
+
+	      dbg_log (_("considering %s entry \"%s\", timeout %" PRIu64),
+		       serv2str[runp->type], str, dh->timeout);
+	    }
+
+	  /* Check whether the entry timed out.  */
+	  if (dh->timeout < now)
+	    {
+	      /* This hash bucket could contain entries which need to
+		 be looked at.  */
+	      mark[cnt] = true;
+
+	      first = MIN (first, cnt);
+	      last = MAX (last, cnt);
+
+	      /* We only have to look at the data of the first entries
+		 since the count information is kept in the data part
+		 which is shared.  */
+	      if (runp->first)
+		{
+
+		  /* At this point there are two choices: we reload the
+		     value or we discard it.  Do not change NRELOADS if
+		     we never not reload the record.  */
+		  if ((reload_count != UINT_MAX
+		       && __builtin_expect (dh->nreloads >= reload_count, 0))
+		      /* We always remove negative entries.  */
+		      || dh->notfound
+		      /* Discard everything if the user explicitly
+			 requests it.  */
+		      || now == LONG_MAX)
+		    {
+		      /* Remove the value.  */
+		      dh->usable = false;
+
+		      /* We definitely have some garbage entries now.  */
+		      any = true;
+		    }
+		  else
+		    {
+		      /* Reload the value.  We do this only for the
+			 initially used key, not the additionally
+			 added derived value.  */
+		      assert (runp->type < LASTREQ
+			      && readdfcts[runp->type] != NULL);
+
+		      time_t timeout = readdfcts[runp->type] (table, runp, dh);
+		      next_timeout = MIN (next_timeout, timeout);
+
+		      /* If the entry has been replaced, we might need
+			 cleanup.  */
+		      any |= !dh->usable;
+		    }
+		}
+	    }
+	  else
+	    {
+	      assert (dh->usable);
+	      next_timeout = MIN (next_timeout, dh->timeout);
+	    }
+
+	  run = runp->next;
+	}
+    }
+  while (cnt > 0);
+
+  if (__glibc_unlikely (fd != -1))
+    {
+      /* Reply to the INVALIDATE initiator that the cache has been
+	 invalidated.  */
+      int32_t resp = 0;
+      writeall (fd, &resp, sizeof (resp));
+    }
+
+  if (first <= last)
+    {
+      struct hashentry *head = NULL;
+
+      /* Now we have to get the write lock since we are about to modify
+	 the table.  */
+      if (__glibc_unlikely (pthread_rwlock_trywrlock (&table->lock) != 0))
+	{
+	  ++table->head->wrlockdelayed;
+	  pthread_rwlock_wrlock (&table->lock);
+	}
+
+      while (first <= last)
+	{
+	  if (mark[first])
+	    {
+	      ref_t *old = &table->head->array[first];
+	      ref_t run = table->head->array[first];
+
+	      assert (run != ENDREF);
+	      do
+		{
+		  struct hashentry *runp = (struct hashentry *) (data + run);
+		  struct datahead *dh
+		    = (struct datahead *) (data + runp->packet);
+
+		  if (! dh->usable)
+		    {
+		      /* We need the list only for debugging but it is
+			 more costly to avoid creating the list than
+			 doing it.  */
+		      runp->dellist = head;
+		      head = runp;
+
+		      /* No need for an atomic operation, we have the
+			 write lock.  */
+		      --table->head->nentries;
+
+		      run = *old = runp->next;
+		    }
+		  else
+		    {
+		      old = &runp->next;
+		      run = runp->next;
+		    }
+		}
+	      while (run != ENDREF);
+	    }
+
+	  ++first;
+	}
+
+      /* It's all done.  */
+      pthread_rwlock_unlock (&table->lock);
+
+      /* Make sure the data is saved to disk.  */
+      if (table->persistent)
+	msync (table->head,
+	       data + table->head->first_free - (char *) table->head,
+	       MS_ASYNC);
+
+      /* One extra pass if we do debugging.  */
+      if (__glibc_unlikely (debug_level > 0))
+	{
+	  struct hashentry *runp = head;
+
+	  while (runp != NULL)
+	    {
+	      char buf[INET6_ADDRSTRLEN];
+	      const char *str;
+
+	      if (runp->type == GETHOSTBYADDR || runp->type == GETHOSTBYADDRv6)
+		{
+		  inet_ntop (runp->type == GETHOSTBYADDR ? AF_INET : AF_INET6,
+			     data + runp->key, buf, sizeof (buf));
+		  str = buf;
+		}
+	      else
+		str = data + runp->key;
+
+	      dbg_log ("remove %s entry \"%s\"", serv2str[runp->type], str);
+
+	      runp = runp->dellist;
+	    }
+	}
+    }
+
+  if (__glibc_unlikely (! mark_use_alloca))
+    free (mark);
+
+  /* Run garbage collection if any entry has been removed or replaced.  */
+  if (any)
+    gc (table);
+
+  /* If there is no entry in the database and we therefore have no new
+     timeout value, tell the caller to wake up in 24 hours.  */
+  return next_timeout == NO_TIMEOUT ? 24 * 60 * 60 : next_timeout - now;
+}