Blame nscd/servicescache.c

Packit 6c4009
/* Cache handling for services lookup.
Packit 6c4009
   Copyright (C) 2007-2018 Free Software Foundation, Inc.
Packit 6c4009
   This file is part of the GNU C Library.
Packit 6c4009
   Contributed by Ulrich Drepper <drepper@drepper.com>, 2007.
Packit 6c4009
Packit 6c4009
   This program is free software; you can redistribute it and/or modify
Packit 6c4009
   it under the terms of the GNU General Public License as published
Packit 6c4009
   by the Free Software Foundation; version 2 of the License, or
Packit 6c4009
   (at your option) any later version.
Packit 6c4009
Packit 6c4009
   This program is distributed in the hope that it will be useful,
Packit 6c4009
   but WITHOUT ANY WARRANTY; without even the implied warranty of
Packit 6c4009
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
Packit 6c4009
   GNU General Public License for more details.
Packit 6c4009
Packit 6c4009
   You should have received a copy of the GNU General Public License
Packit 6c4009
   along with this program; if not, see <http://www.gnu.org/licenses/>.  */
Packit 6c4009
Packit 6c4009
#include <assert.h>
Packit 6c4009
#include <errno.h>
Packit 6c4009
#include <libintl.h>
Packit 6c4009
#include <netdb.h>
Packit 6c4009
#include <unistd.h>
Packit 6c4009
#include <stdint.h>
Packit 6c4009
#include <sys/mman.h>
Packit 6c4009
#include <kernel-features.h>
Packit 6c4009
#include <scratch_buffer.h>
Packit 6c4009
Packit 6c4009
#include "nscd.h"
Packit 6c4009
#include "dbg_log.h"
Packit 6c4009
Packit 6c4009
Packit 6c4009
/* This is the standard reply in case the service is disabled.  */
Packit 6c4009
static const serv_response_header disabled =
Packit 6c4009
{
Packit 6c4009
  .version = NSCD_VERSION,
Packit 6c4009
  .found = -1,
Packit 6c4009
  .s_name_len = 0,
Packit 6c4009
  .s_proto_len = 0,
Packit 6c4009
  .s_aliases_cnt = 0,
Packit 6c4009
  .s_port = -1
Packit 6c4009
};
Packit 6c4009
Packit 6c4009
/* This is the struct describing how to write this record.  */
Packit 6c4009
const struct iovec serv_iov_disabled =
Packit 6c4009
{
Packit 6c4009
  .iov_base = (void *) &disabled,
Packit 6c4009
  .iov_len = sizeof (disabled)
Packit 6c4009
};
Packit 6c4009
Packit 6c4009
Packit 6c4009
/* This is the standard reply in case we haven't found the dataset.  */
Packit 6c4009
static const serv_response_header notfound =
Packit 6c4009
{
Packit 6c4009
  .version = NSCD_VERSION,
Packit 6c4009
  .found = 0,
Packit 6c4009
  .s_name_len = 0,
Packit 6c4009
  .s_proto_len = 0,
Packit 6c4009
  .s_aliases_cnt = 0,
Packit 6c4009
  .s_port = -1
Packit 6c4009
};
Packit 6c4009
Packit 6c4009
Packit 6c4009
static time_t
Packit 6c4009
cache_addserv (struct database_dyn *db, int fd, request_header *req,
Packit 6c4009
	       const void *key, struct servent *serv, uid_t owner,
Packit 6c4009
	       struct hashentry *const he, struct datahead *dh, int errval)
Packit 6c4009
{
Packit 6c4009
  bool all_written = true;
Packit 6c4009
  ssize_t total;
Packit 6c4009
  time_t t = time (NULL);
Packit 6c4009
Packit 6c4009
  /* We allocate all data in one memory block: the iov vector,
Packit 6c4009
     the response header and the dataset itself.  */
Packit 6c4009
  struct dataset
Packit 6c4009
  {
Packit 6c4009
    struct datahead head;
Packit 6c4009
    serv_response_header resp;
Packit 6c4009
    char strdata[0];
Packit 6c4009
  } *dataset;
Packit 6c4009
Packit 6c4009
  assert (offsetof (struct dataset, resp) == offsetof (struct datahead, data));
Packit 6c4009
Packit 6c4009
  time_t timeout = MAX_TIMEOUT_VALUE;
Packit 6c4009
  if (serv == NULL)
Packit 6c4009
    {
Packit 6c4009
      if (he != NULL && errval == EAGAIN)
Packit 6c4009
	{
Packit 6c4009
	  /* If we have an old record available but cannot find one
Packit 6c4009
	     now because the service is not available we keep the old
Packit 6c4009
	     record and make sure it does not get removed.  */
Packit 6c4009
	  if (reload_count != UINT_MAX)
Packit 6c4009
	    /* Do not reset the value if we never not reload the record.  */
Packit 6c4009
	    dh->nreloads = reload_count - 1;
Packit 6c4009
Packit 6c4009
	  /* Reload with the same time-to-live value.  */
Packit 6c4009
	  timeout = dh->timeout = t + db->postimeout;
Packit 6c4009
Packit 6c4009
	  total = 0;
Packit 6c4009
	}
Packit 6c4009
      else
Packit 6c4009
	{
Packit 6c4009
	  /* We have no data.  This means we send the standard reply for this
Packit 6c4009
	     case.  */
Packit 6c4009
	  total = sizeof (notfound);
Packit 6c4009
Packit 6c4009
	  if (fd != -1
Packit 6c4009
	      && TEMP_FAILURE_RETRY (send (fd, &notfound, total,
Packit 6c4009
					   MSG_NOSIGNAL)) != total)
Packit 6c4009
	    all_written = false;
Packit 6c4009
Packit 6c4009
	  /* If we have a transient error or cannot permanently store
Packit 6c4009
	     the result, so be it.  */
Packit 6c4009
	  if (errval == EAGAIN || __builtin_expect (db->negtimeout == 0, 0))
Packit 6c4009
	    {
Packit 6c4009
	      /* Mark the old entry as obsolete.  */
Packit 6c4009
	      if (dh != NULL)
Packit 6c4009
		dh->usable = false;
Packit 6c4009
	    }
Packit 6c4009
	  else if ((dataset = mempool_alloc (db, (sizeof (struct dataset)
Packit 6c4009
						  + req->key_len), 1)) != NULL)
Packit 6c4009
	    {
Packit 6c4009
	      timeout = datahead_init_neg (&dataset->head,
Packit 6c4009
					   (sizeof (struct dataset)
Packit 6c4009
					    + req->key_len), total,
Packit 6c4009
					   db->negtimeout);
Packit 6c4009
Packit 6c4009
	      /* This is the reply.  */
Packit 6c4009
	      memcpy (&dataset->resp, &notfound, total);
Packit 6c4009
Packit 6c4009
	      /* Copy the key data.  */
Packit 6c4009
	      memcpy (dataset->strdata, key, req->key_len);
Packit 6c4009
Packit 6c4009
	      /* If necessary, we also propagate the data to disk.  */
Packit 6c4009
	      if (db->persistent)
Packit 6c4009
		{
Packit 6c4009
		  // XXX async OK?
Packit 6c4009
		  uintptr_t pval = (uintptr_t) dataset & ~pagesize_m1;
Packit 6c4009
		  msync ((void *) pval,
Packit 6c4009
			 ((uintptr_t) dataset & pagesize_m1)
Packit 6c4009
			 + sizeof (struct dataset) + req->key_len, MS_ASYNC);
Packit 6c4009
		}
Packit 6c4009
Packit 6c4009
	      (void) cache_add (req->type, &dataset->strdata, req->key_len,
Packit 6c4009
				&dataset->head, true, db, owner, he == NULL);
Packit 6c4009
Packit 6c4009
	      pthread_rwlock_unlock (&db->lock);
Packit 6c4009
Packit 6c4009
	      /* Mark the old entry as obsolete.  */
Packit 6c4009
	      if (dh != NULL)
Packit 6c4009
		dh->usable = false;
Packit 6c4009
	    }
Packit 6c4009
	}
Packit 6c4009
    }
Packit 6c4009
  else
Packit 6c4009
    {
Packit 6c4009
      /* Determine the I/O structure.  */
Packit 6c4009
      size_t s_name_len = strlen (serv->s_name) + 1;
Packit 6c4009
      size_t s_proto_len = strlen (serv->s_proto) + 1;
Packit 6c4009
      uint32_t *s_aliases_len;
Packit 6c4009
      size_t s_aliases_cnt;
Packit 6c4009
      char *aliases;
Packit 6c4009
      char *cp;
Packit 6c4009
      size_t cnt;
Packit 6c4009
Packit 6c4009
      /* Determine the number of aliases.  */
Packit 6c4009
      s_aliases_cnt = 0;
Packit 6c4009
      for (cnt = 0; serv->s_aliases[cnt] != NULL; ++cnt)
Packit 6c4009
	++s_aliases_cnt;
Packit 6c4009
      /* Determine the length of all aliases.  */
Packit 6c4009
      s_aliases_len = (uint32_t *) alloca (s_aliases_cnt * sizeof (uint32_t));
Packit 6c4009
      total = 0;
Packit 6c4009
      for (cnt = 0; cnt < s_aliases_cnt; ++cnt)
Packit 6c4009
	{
Packit 6c4009
	  s_aliases_len[cnt] = strlen (serv->s_aliases[cnt]) + 1;
Packit 6c4009
	  total += s_aliases_len[cnt];
Packit 6c4009
	}
Packit 6c4009
Packit 6c4009
      total += (offsetof (struct dataset, strdata)
Packit 6c4009
		+ s_name_len
Packit 6c4009
		+ s_proto_len
Packit 6c4009
		+ s_aliases_cnt * sizeof (uint32_t));
Packit 6c4009
Packit 6c4009
      /* If we refill the cache, first assume the reconrd did not
Packit 6c4009
	 change.  Allocate memory on the cache since it is likely
Packit 6c4009
	 discarded anyway.  If it turns out to be necessary to have a
Packit 6c4009
	 new record we can still allocate real memory.  */
Packit 6c4009
      bool alloca_used = false;
Packit 6c4009
      dataset = NULL;
Packit 6c4009
Packit 6c4009
      if (he == NULL)
Packit 6c4009
	dataset = (struct dataset *) mempool_alloc (db, total + req->key_len,
Packit 6c4009
						    1);
Packit 6c4009
Packit 6c4009
      if (dataset == NULL)
Packit 6c4009
	{
Packit 6c4009
	  /* We cannot permanently add the result in the moment.  But
Packit 6c4009
	     we can provide the result as is.  Store the data in some
Packit 6c4009
	     temporary memory.  */
Packit 6c4009
	  dataset = (struct dataset *) alloca (total + req->key_len);
Packit 6c4009
Packit 6c4009
	  /* We cannot add this record to the permanent database.  */
Packit 6c4009
	  alloca_used = true;
Packit 6c4009
	}
Packit 6c4009
Packit 6c4009
      timeout = datahead_init_pos (&dataset->head, total + req->key_len,
Packit 6c4009
				   total - offsetof (struct dataset, resp),
Packit 6c4009
				   he == NULL ? 0 : dh->nreloads + 1,
Packit 6c4009
				   db->postimeout);
Packit 6c4009
Packit 6c4009
      dataset->resp.version = NSCD_VERSION;
Packit 6c4009
      dataset->resp.found = 1;
Packit 6c4009
      dataset->resp.s_name_len = s_name_len;
Packit 6c4009
      dataset->resp.s_proto_len = s_proto_len;
Packit 6c4009
      dataset->resp.s_port = serv->s_port;
Packit 6c4009
      dataset->resp.s_aliases_cnt = s_aliases_cnt;
Packit 6c4009
Packit 6c4009
      cp = dataset->strdata;
Packit 6c4009
Packit 6c4009
      cp = mempcpy (cp, serv->s_name, s_name_len);
Packit 6c4009
      cp = mempcpy (cp, serv->s_proto, s_proto_len);
Packit 6c4009
      cp = mempcpy (cp, s_aliases_len, s_aliases_cnt * sizeof (uint32_t));
Packit 6c4009
Packit 6c4009
      /* Then the aliases.  */
Packit 6c4009
      aliases = cp;
Packit 6c4009
      for (cnt = 0; cnt < s_aliases_cnt; ++cnt)
Packit 6c4009
	cp = mempcpy (cp, serv->s_aliases[cnt], s_aliases_len[cnt]);
Packit 6c4009
Packit 6c4009
      assert (cp
Packit 6c4009
	      == dataset->strdata + total - offsetof (struct dataset,
Packit 6c4009
						      strdata));
Packit 6c4009
Packit 6c4009
      char *key_copy = memcpy (cp, key, req->key_len);
Packit 6c4009
Packit 6c4009
      /* Now we can determine whether on refill we have to create a new
Packit 6c4009
	 record or not.  */
Packit 6c4009
      if (he != NULL)
Packit 6c4009
	{
Packit 6c4009
	  assert (fd == -1);
Packit 6c4009
Packit 6c4009
	  if (total + req->key_len == dh->allocsize
Packit 6c4009
	      && total - offsetof (struct dataset, resp) == dh->recsize
Packit 6c4009
	      && memcmp (&dataset->resp, dh->data,
Packit 6c4009
			 dh->allocsize - offsetof (struct dataset, resp)) == 0)
Packit 6c4009
	    {
Packit 6c4009
	      /* The data has not changed.  We will just bump the
Packit 6c4009
		 timeout value.  Note that the new record has been
Packit 6c4009
		 allocated on the stack and need not be freed.  */
Packit 6c4009
	      dh->timeout = dataset->head.timeout;
Packit 6c4009
	      ++dh->nreloads;
Packit 6c4009
	    }
Packit 6c4009
	  else
Packit 6c4009
	    {
Packit 6c4009
	      /* We have to create a new record.  Just allocate
Packit 6c4009
		 appropriate memory and copy it.  */
Packit 6c4009
	      struct dataset *newp
Packit 6c4009
		= (struct dataset *) mempool_alloc (db, total + req->key_len,
Packit 6c4009
						    1);
Packit 6c4009
	      if (newp != NULL)
Packit 6c4009
		{
Packit 6c4009
		  /* Adjust pointers into the memory block.  */
Packit 6c4009
		  aliases = (char *) newp + (aliases - (char *) dataset);
Packit 6c4009
		  assert (key_copy != NULL);
Packit 6c4009
		  key_copy = (char *) newp + (key_copy - (char *) dataset);
Packit 6c4009
Packit 6c4009
		  dataset = memcpy (newp, dataset, total + req->key_len);
Packit 6c4009
		  alloca_used = false;
Packit 6c4009
		}
Packit 6c4009
Packit 6c4009
	      /* Mark the old record as obsolete.  */
Packit 6c4009
	      dh->usable = false;
Packit 6c4009
	    }
Packit 6c4009
	}
Packit 6c4009
      else
Packit 6c4009
	{
Packit 6c4009
	  /* We write the dataset before inserting it to the database
Packit 6c4009
	     since while inserting this thread might block and so would
Packit 6c4009
	     unnecessarily keep the receiver waiting.  */
Packit 6c4009
	  assert (fd != -1);
Packit 6c4009
Packit 6c4009
	  if (writeall (fd, &dataset->resp, dataset->head.recsize)
Packit 6c4009
	      != dataset->head.recsize)
Packit 6c4009
	    all_written = false;
Packit 6c4009
	}
Packit 6c4009
Packit 6c4009
      /* Add the record to the database.  But only if it has not been
Packit 6c4009
	 stored on the stack.  */
Packit 6c4009
      if (! alloca_used)
Packit 6c4009
	{
Packit 6c4009
	  /* If necessary, we also propagate the data to disk.  */
Packit 6c4009
	  if (db->persistent)
Packit 6c4009
	    {
Packit 6c4009
	      // XXX async OK?
Packit 6c4009
	      uintptr_t pval = (uintptr_t) dataset & ~pagesize_m1;
Packit 6c4009
	      msync ((void *) pval,
Packit 6c4009
		     ((uintptr_t) dataset & pagesize_m1)
Packit 6c4009
		     + total + req->key_len, MS_ASYNC);
Packit 6c4009
	    }
Packit 6c4009
Packit 6c4009
	  (void) cache_add (req->type, key_copy, req->key_len,
Packit 6c4009
			    &dataset->head, true, db, owner, he == NULL);
Packit 6c4009
Packit 6c4009
	  pthread_rwlock_unlock (&db->lock);
Packit 6c4009
	}
Packit 6c4009
    }
Packit 6c4009
Packit 6c4009
  if (__builtin_expect (!all_written, 0) && debug_level > 0)
Packit 6c4009
    {
Packit 6c4009
      char buf[256];
Packit 6c4009
      dbg_log (_("short write in %s: %s"),  __FUNCTION__,
Packit 6c4009
	       strerror_r (errno, buf, sizeof (buf)));
Packit 6c4009
    }
Packit 6c4009
Packit 6c4009
  return timeout;
Packit 6c4009
}
Packit 6c4009
Packit 6c4009
Packit 6c4009
static int
Packit 6c4009
lookup (int type, char *key, struct servent *resultbufp, char *buffer,
Packit 6c4009
	size_t buflen, struct servent **serv)
Packit 6c4009
{
Packit 6c4009
  char *proto = strrchr (key, '/');
Packit 6c4009
  if (proto != NULL && proto != key)
Packit 6c4009
    {
Packit 6c4009
      key = strndupa (key, proto - key);
Packit 6c4009
      if (proto[1] == '\0')
Packit 6c4009
	proto = NULL;
Packit 6c4009
      else
Packit 6c4009
	++proto;
Packit 6c4009
    }
Packit 6c4009
Packit 6c4009
  if (type == GETSERVBYNAME)
Packit 6c4009
    return __getservbyname_r (key, proto, resultbufp, buffer, buflen, serv);
Packit 6c4009
Packit 6c4009
  assert (type == GETSERVBYPORT);
Packit 6c4009
  return __getservbyport_r (atol (key), proto, resultbufp, buffer, buflen,
Packit 6c4009
			    serv);
Packit 6c4009
}
Packit 6c4009
Packit 6c4009
Packit 6c4009
static time_t
Packit 6c4009
addservbyX (struct database_dyn *db, int fd, request_header *req,
Packit 6c4009
	    char *key, uid_t uid, struct hashentry *he, struct datahead *dh)
Packit 6c4009
{
Packit 6c4009
  /* Search for the entry matching the key.  Please note that we don't
Packit 6c4009
     look again in the table whether the dataset is now available.  We
Packit 6c4009
     simply insert it.  It does not matter if it is in there twice.  The
Packit 6c4009
     pruning function only will look at the timestamp.  */
Packit 6c4009
  struct servent resultbuf;
Packit 6c4009
  struct servent *serv;
Packit 6c4009
  int errval = 0;
Packit 6c4009
  struct scratch_buffer tmpbuf;
Packit 6c4009
  scratch_buffer_init (&tmpbuf);
Packit 6c4009
Packit 6c4009
  if (__glibc_unlikely (debug_level > 0))
Packit 6c4009
    {
Packit 6c4009
      if (he == NULL)
Packit 6c4009
	dbg_log (_("Haven't found \"%s\" in services cache!"), key);
Packit 6c4009
      else
Packit 6c4009
	dbg_log (_("Reloading \"%s\" in services cache!"), key);
Packit 6c4009
    }
Packit 6c4009
Packit 6c4009
  while (lookup (req->type, key, &resultbuf,
Packit 6c4009
		 tmpbuf.data, tmpbuf.length, &serv) != 0
Packit 6c4009
	 && (errval = errno) == ERANGE)
Packit 6c4009
    if (!scratch_buffer_grow (&tmpbuf))
Packit 6c4009
      {
Packit 6c4009
	/* We ran out of memory.  We cannot do anything but sending a
Packit 6c4009
	   negative response.  In reality this should never
Packit 6c4009
	   happen.  */
Packit 6c4009
	serv = NULL;
Packit 6c4009
	/* We set the error to indicate this is (possibly) a temporary
Packit 6c4009
	   error and that it does not mean the entry is not available
Packit 6c4009
	   at all.  */
Packit 6c4009
	errval = EAGAIN;
Packit 6c4009
	break;
Packit 6c4009
      }
Packit 6c4009
Packit 6c4009
  time_t timeout = cache_addserv (db, fd, req, key, serv, uid, he, dh, errval);
Packit 6c4009
  scratch_buffer_free (&tmpbuf);
Packit 6c4009
  return timeout;
Packit 6c4009
}
Packit 6c4009
Packit 6c4009
Packit 6c4009
void
Packit 6c4009
addservbyname (struct database_dyn *db, int fd, request_header *req,
Packit 6c4009
	       void *key, uid_t uid)
Packit 6c4009
{
Packit 6c4009
  addservbyX (db, fd, req, key, uid, NULL, NULL);
Packit 6c4009
}
Packit 6c4009
Packit 6c4009
Packit 6c4009
time_t
Packit 6c4009
readdservbyname (struct database_dyn *db, struct hashentry *he,
Packit 6c4009
		 struct datahead *dh)
Packit 6c4009
{
Packit 6c4009
  request_header req =
Packit 6c4009
    {
Packit 6c4009
      .type = GETSERVBYNAME,
Packit 6c4009
      .key_len = he->len
Packit 6c4009
    };
Packit 6c4009
Packit 6c4009
  return addservbyX (db, -1, &req, db->data + he->key, he->owner, he, dh);
Packit 6c4009
}
Packit 6c4009
Packit 6c4009
Packit 6c4009
void
Packit 6c4009
addservbyport (struct database_dyn *db, int fd, request_header *req,
Packit 6c4009
	       void *key, uid_t uid)
Packit 6c4009
{
Packit 6c4009
  addservbyX (db, fd, req, key, uid, NULL, NULL);
Packit 6c4009
}
Packit 6c4009
Packit 6c4009
Packit 6c4009
time_t
Packit 6c4009
readdservbyport (struct database_dyn *db, struct hashentry *he,
Packit 6c4009
		 struct datahead *dh)
Packit 6c4009
{
Packit 6c4009
  request_header req =
Packit 6c4009
    {
Packit 6c4009
      .type = GETSERVBYPORT,
Packit 6c4009
      .key_len = he->len
Packit 6c4009
    };
Packit 6c4009
Packit 6c4009
  return addservbyX (db, -1, &req, db->data + he->key, he->owner, he, dh);
Packit 6c4009
}