(root)/
glibc-2.38/
nscd/
servicescache.c
       1  /* Cache handling for services lookup.
       2     Copyright (C) 2007-2023 Free Software Foundation, Inc.
       3     This file is part of the GNU C Library.
       4  
       5     This program is free software; you can redistribute it and/or modify
       6     it under the terms of the GNU General Public License as published
       7     by the Free Software Foundation; version 2 of the License, or
       8     (at your option) any later version.
       9  
      10     This program is distributed in the hope that it will be useful,
      11     but WITHOUT ANY WARRANTY; without even the implied warranty of
      12     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      13     GNU General Public License for more details.
      14  
      15     You should have received a copy of the GNU General Public License
      16     along with this program; if not, see <https://www.gnu.org/licenses/>.  */
      17  
      18  #include <assert.h>
      19  #include <errno.h>
      20  #include <libintl.h>
      21  #include <netdb.h>
      22  #include <unistd.h>
      23  #include <stdint.h>
      24  #include <sys/mman.h>
      25  #include <kernel-features.h>
      26  #include <scratch_buffer.h>
      27  
      28  #include "nscd.h"
      29  #include "dbg_log.h"
      30  
      31  
      32  /* This is the standard reply in case the service is disabled.  */
      33  static const serv_response_header disabled =
      34  {
      35    .version = NSCD_VERSION,
      36    .found = -1,
      37    .s_name_len = 0,
      38    .s_proto_len = 0,
      39    .s_aliases_cnt = 0,
      40    .s_port = -1
      41  };
      42  
      43  /* This is the struct describing how to write this record.  */
      44  const struct iovec serv_iov_disabled =
      45  {
      46    .iov_base = (void *) &disabled,
      47    .iov_len = sizeof (disabled)
      48  };
      49  
      50  
      51  /* This is the standard reply in case we haven't found the dataset.  */
      52  static const serv_response_header notfound =
      53  {
      54    .version = NSCD_VERSION,
      55    .found = 0,
      56    .s_name_len = 0,
      57    .s_proto_len = 0,
      58    .s_aliases_cnt = 0,
      59    .s_port = -1
      60  };
      61  
      62  
      63  static time_t
      64  cache_addserv (struct database_dyn *db, int fd, request_header *req,
      65  	       const void *key, struct servent *serv, uid_t owner,
      66  	       struct hashentry *const he, struct datahead *dh, int errval)
      67  {
      68    bool all_written = true;
      69    ssize_t total;
      70    time_t t = time (NULL);
      71  
      72    /* We allocate all data in one memory block: the iov vector,
      73       the response header and the dataset itself.  */
      74    struct dataset
      75    {
      76      struct datahead head;
      77      serv_response_header resp;
      78      char strdata[0];
      79    } *dataset;
      80  
      81    assert (offsetof (struct dataset, resp) == offsetof (struct datahead, data));
      82  
      83    time_t timeout = MAX_TIMEOUT_VALUE;
      84    if (serv == NULL)
      85      {
      86        if (he != NULL && errval == EAGAIN)
      87  	{
      88  	  /* If we have an old record available but cannot find one
      89  	     now because the service is not available we keep the old
      90  	     record and make sure it does not get removed.  */
      91  	  if (reload_count != UINT_MAX)
      92  	    /* Do not reset the value if we never not reload the record.  */
      93  	    dh->nreloads = reload_count - 1;
      94  
      95  	  /* Reload with the same time-to-live value.  */
      96  	  timeout = dh->timeout = t + db->postimeout;
      97  
      98  	  total = 0;
      99  	}
     100        else
     101  	{
     102  	  /* We have no data.  This means we send the standard reply for this
     103  	     case.  */
     104  	  total = sizeof (notfound);
     105  
     106  	  if (fd != -1
     107  	      && TEMP_FAILURE_RETRY (send (fd, &notfound, total,
     108  					   MSG_NOSIGNAL)) != total)
     109  	    all_written = false;
     110  
     111  	  /* If we have a transient error or cannot permanently store
     112  	     the result, so be it.  */
     113  	  if (errval == EAGAIN || __builtin_expect (db->negtimeout == 0, 0))
     114  	    {
     115  	      /* Mark the old entry as obsolete.  */
     116  	      if (dh != NULL)
     117  		dh->usable = false;
     118  	    }
     119  	  else if ((dataset = mempool_alloc (db, (sizeof (struct dataset)
     120  						  + req->key_len), 1)) != NULL)
     121  	    {
     122  	      timeout = datahead_init_neg (&dataset->head,
     123  					   (sizeof (struct dataset)
     124  					    + req->key_len), total,
     125  					   db->negtimeout);
     126  
     127  	      /* This is the reply.  */
     128  	      memcpy (&dataset->resp, &notfound, total);
     129  
     130  	      /* Copy the key data.  */
     131  	      memcpy (dataset->strdata, key, req->key_len);
     132  
     133  	      /* If necessary, we also propagate the data to disk.  */
     134  	      if (db->persistent)
     135  		{
     136  		  // XXX async OK?
     137  		  uintptr_t pval = (uintptr_t) dataset & ~pagesize_m1;
     138  		  msync ((void *) pval,
     139  			 ((uintptr_t) dataset & pagesize_m1)
     140  			 + sizeof (struct dataset) + req->key_len, MS_ASYNC);
     141  		}
     142  
     143  	      (void) cache_add (req->type, &dataset->strdata, req->key_len,
     144  				&dataset->head, true, db, owner, he == NULL);
     145  
     146  	      pthread_rwlock_unlock (&db->lock);
     147  
     148  	      /* Mark the old entry as obsolete.  */
     149  	      if (dh != NULL)
     150  		dh->usable = false;
     151  	    }
     152  	}
     153      }
     154    else
     155      {
     156        /* Determine the I/O structure.  */
     157        size_t s_name_len = strlen (serv->s_name) + 1;
     158        size_t s_proto_len = strlen (serv->s_proto) + 1;
     159        uint32_t *s_aliases_len;
     160        size_t s_aliases_cnt;
     161        char *aliases;
     162        char *cp;
     163        size_t cnt;
     164  
     165        /* Determine the number of aliases.  */
     166        s_aliases_cnt = 0;
     167        for (cnt = 0; serv->s_aliases[cnt] != NULL; ++cnt)
     168  	++s_aliases_cnt;
     169        /* Determine the length of all aliases.  */
     170        s_aliases_len = (uint32_t *) alloca (s_aliases_cnt * sizeof (uint32_t));
     171        total = 0;
     172        for (cnt = 0; cnt < s_aliases_cnt; ++cnt)
     173  	{
     174  	  s_aliases_len[cnt] = strlen (serv->s_aliases[cnt]) + 1;
     175  	  total += s_aliases_len[cnt];
     176  	}
     177  
     178        total += (offsetof (struct dataset, strdata)
     179  		+ s_name_len
     180  		+ s_proto_len
     181  		+ s_aliases_cnt * sizeof (uint32_t));
     182  
     183        /* If we refill the cache, first assume the reconrd did not
     184  	 change.  Allocate memory on the cache since it is likely
     185  	 discarded anyway.  If it turns out to be necessary to have a
     186  	 new record we can still allocate real memory.  */
     187        bool alloca_used = false;
     188        dataset = NULL;
     189  
     190        if (he == NULL)
     191  	dataset = (struct dataset *) mempool_alloc (db, total + req->key_len,
     192  						    1);
     193  
     194        if (dataset == NULL)
     195  	{
     196  	  /* We cannot permanently add the result in the moment.  But
     197  	     we can provide the result as is.  Store the data in some
     198  	     temporary memory.  */
     199  	  dataset = (struct dataset *) alloca (total + req->key_len);
     200  
     201  	  /* We cannot add this record to the permanent database.  */
     202  	  alloca_used = true;
     203  	}
     204  
     205        timeout = datahead_init_pos (&dataset->head, total + req->key_len,
     206  				   total - offsetof (struct dataset, resp),
     207  				   he == NULL ? 0 : dh->nreloads + 1,
     208  				   db->postimeout);
     209  
     210        dataset->resp.version = NSCD_VERSION;
     211        dataset->resp.found = 1;
     212        dataset->resp.s_name_len = s_name_len;
     213        dataset->resp.s_proto_len = s_proto_len;
     214        dataset->resp.s_port = serv->s_port;
     215        dataset->resp.s_aliases_cnt = s_aliases_cnt;
     216  
     217        cp = dataset->strdata;
     218  
     219        cp = mempcpy (cp, serv->s_name, s_name_len);
     220        cp = mempcpy (cp, serv->s_proto, s_proto_len);
     221        cp = mempcpy (cp, s_aliases_len, s_aliases_cnt * sizeof (uint32_t));
     222  
     223        /* Then the aliases.  */
     224        aliases = cp;
     225        for (cnt = 0; cnt < s_aliases_cnt; ++cnt)
     226  	cp = mempcpy (cp, serv->s_aliases[cnt], s_aliases_len[cnt]);
     227  
     228        assert (cp
     229  	      == dataset->strdata + total - offsetof (struct dataset,
     230  						      strdata));
     231  
     232        char *key_copy = memcpy (cp, key, req->key_len);
     233  
     234        /* Now we can determine whether on refill we have to create a new
     235  	 record or not.  */
     236        if (he != NULL)
     237  	{
     238  	  assert (fd == -1);
     239  
     240  	  if (total + req->key_len == dh->allocsize
     241  	      && total - offsetof (struct dataset, resp) == dh->recsize
     242  	      && memcmp (&dataset->resp, dh->data,
     243  			 dh->allocsize - offsetof (struct dataset, resp)) == 0)
     244  	    {
     245  	      /* The data has not changed.  We will just bump the
     246  		 timeout value.  Note that the new record has been
     247  		 allocated on the stack and need not be freed.  */
     248  	      dh->timeout = dataset->head.timeout;
     249  	      ++dh->nreloads;
     250  	    }
     251  	  else
     252  	    {
     253  	      /* We have to create a new record.  Just allocate
     254  		 appropriate memory and copy it.  */
     255  	      struct dataset *newp
     256  		= (struct dataset *) mempool_alloc (db, total + req->key_len,
     257  						    1);
     258  	      if (newp != NULL)
     259  		{
     260  		  /* Adjust pointers into the memory block.  */
     261  		  aliases = (char *) newp + (aliases - (char *) dataset);
     262  		  assert (key_copy != NULL);
     263  		  key_copy = (char *) newp + (key_copy - (char *) dataset);
     264  
     265  		  dataset = memcpy (newp, dataset, total + req->key_len);
     266  		  alloca_used = false;
     267  		}
     268  
     269  	      /* Mark the old record as obsolete.  */
     270  	      dh->usable = false;
     271  	    }
     272  	}
     273        else
     274  	{
     275  	  /* We write the dataset before inserting it to the database
     276  	     since while inserting this thread might block and so would
     277  	     unnecessarily keep the receiver waiting.  */
     278  	  assert (fd != -1);
     279  
     280  	  if (writeall (fd, &dataset->resp, dataset->head.recsize)
     281  	      != dataset->head.recsize)
     282  	    all_written = false;
     283  	}
     284  
     285        /* Add the record to the database.  But only if it has not been
     286  	 stored on the stack.  */
     287        if (! alloca_used)
     288  	{
     289  	  /* If necessary, we also propagate the data to disk.  */
     290  	  if (db->persistent)
     291  	    {
     292  	      // XXX async OK?
     293  	      uintptr_t pval = (uintptr_t) dataset & ~pagesize_m1;
     294  	      msync ((void *) pval,
     295  		     ((uintptr_t) dataset & pagesize_m1)
     296  		     + total + req->key_len, MS_ASYNC);
     297  	    }
     298  
     299  	  (void) cache_add (req->type, key_copy, req->key_len,
     300  			    &dataset->head, true, db, owner, he == NULL);
     301  
     302  	  pthread_rwlock_unlock (&db->lock);
     303  	}
     304      }
     305  
     306    if (__builtin_expect (!all_written, 0) && debug_level > 0)
     307      {
     308        char buf[256];
     309        dbg_log (_("short write in %s: %s"),  __FUNCTION__,
     310  	       strerror_r (errno, buf, sizeof (buf)));
     311      }
     312  
     313    return timeout;
     314  }
     315  
     316  
     317  static int
     318  lookup (int type, char *key, struct servent *resultbufp, char *buffer,
     319  	size_t buflen, struct servent **serv)
     320  {
     321    char *proto = strrchr (key, '/');
     322    if (proto != NULL && proto != key)
     323      {
     324        key = strndupa (key, proto - key);
     325        if (proto[1] == '\0')
     326  	proto = NULL;
     327        else
     328  	++proto;
     329      }
     330  
     331    if (type == GETSERVBYNAME)
     332      return __getservbyname_r (key, proto, resultbufp, buffer, buflen, serv);
     333  
     334    assert (type == GETSERVBYPORT);
     335    return __getservbyport_r (atol (key), proto, resultbufp, buffer, buflen,
     336  			    serv);
     337  }
     338  
     339  
     340  static time_t
     341  addservbyX (struct database_dyn *db, int fd, request_header *req,
     342  	    char *key, uid_t uid, struct hashentry *he, struct datahead *dh)
     343  {
     344    /* Search for the entry matching the key.  Please note that we don't
     345       look again in the table whether the dataset is now available.  We
     346       simply insert it.  It does not matter if it is in there twice.  The
     347       pruning function only will look at the timestamp.  */
     348    struct servent resultbuf;
     349    struct servent *serv;
     350    int errval = 0;
     351    struct scratch_buffer tmpbuf;
     352    scratch_buffer_init (&tmpbuf);
     353  
     354    if (__glibc_unlikely (debug_level > 0))
     355      {
     356        if (he == NULL)
     357  	dbg_log (_("Haven't found \"%s\" in services cache!"), key);
     358        else
     359  	dbg_log (_("Reloading \"%s\" in services cache!"), key);
     360      }
     361  
     362    while (lookup (req->type, key, &resultbuf,
     363  		 tmpbuf.data, tmpbuf.length, &serv) != 0
     364  	 && (errval = errno) == ERANGE)
     365      if (!scratch_buffer_grow (&tmpbuf))
     366        {
     367  	/* We ran out of memory.  We cannot do anything but sending a
     368  	   negative response.  In reality this should never
     369  	   happen.  */
     370  	serv = NULL;
     371  	/* We set the error to indicate this is (possibly) a temporary
     372  	   error and that it does not mean the entry is not available
     373  	   at all.  */
     374  	errval = EAGAIN;
     375  	break;
     376        }
     377  
     378    time_t timeout = cache_addserv (db, fd, req, key, serv, uid, he, dh, errval);
     379    scratch_buffer_free (&tmpbuf);
     380    return timeout;
     381  }
     382  
     383  
     384  void
     385  addservbyname (struct database_dyn *db, int fd, request_header *req,
     386  	       void *key, uid_t uid)
     387  {
     388    addservbyX (db, fd, req, key, uid, NULL, NULL);
     389  }
     390  
     391  
     392  time_t
     393  readdservbyname (struct database_dyn *db, struct hashentry *he,
     394  		 struct datahead *dh)
     395  {
     396    request_header req =
     397      {
     398        .type = GETSERVBYNAME,
     399        .key_len = he->len
     400      };
     401  
     402    return addservbyX (db, -1, &req, db->data + he->key, he->owner, he, dh);
     403  }
     404  
     405  
     406  void
     407  addservbyport (struct database_dyn *db, int fd, request_header *req,
     408  	       void *key, uid_t uid)
     409  {
     410    addservbyX (db, fd, req, key, uid, NULL, NULL);
     411  }
     412  
     413  
     414  time_t
     415  readdservbyport (struct database_dyn *db, struct hashentry *he,
     416  		 struct datahead *dh)
     417  {
     418    request_header req =
     419      {
     420        .type = GETSERVBYPORT,
     421        .key_len = he->len
     422      };
     423  
     424    return addservbyX (db, -1, &req, db->data + he->key, he->owner, he, dh);
     425  }