Back to index

glibc  2.9
servicescache.c
Go to the documentation of this file.
00001 /* Cache handling for services lookup.
00002    Copyright (C) 2007, 2008 Free Software Foundation, Inc.
00003    This file is part of the GNU C Library.
00004    Contributed by Ulrich Drepper <drepper@drepper.com>, 2007.
00005 
00006    This program is free software; you can redistribute it and/or modify
00007    it under the terms of the GNU General Public License as published
00008    by the Free Software Foundation; version 2 of the License, or
00009    (at your option) any later version.
00010 
00011    This program is distributed in the hope that it will be useful,
00012    but WITHOUT ANY WARRANTY; without even the implied warranty of
00013    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00014    GNU General Public License for more details.
00015 
00016    You should have received a copy of the GNU General Public License
00017    along with this program; if not, write to the Free Software Foundation,
00018    Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.  */
00019 
00020 #include <alloca.h>
00021 #include <assert.h>
00022 #include <errno.h>
00023 #include <libintl.h>
00024 #include <netdb.h>
00025 #include <unistd.h>
00026 #include <sys/mman.h>
00027 #include <kernel-features.h>
00028 
00029 #include "nscd.h"
00030 #include "dbg_log.h"
00031 
00032 
00033 /* This is the standard reply in case the service is disabled.  */
00034 static const serv_response_header disabled =
00035 {
00036   .version = NSCD_VERSION,
00037   .found = -1,
00038   .s_name_len = 0,
00039   .s_proto_len = 0,
00040   .s_aliases_cnt = 0,
00041   .s_port = -1
00042 };
00043 
00044 /* This is the struct describing how to write this record.  */
00045 const struct iovec serv_iov_disabled =
00046 {
00047   .iov_base = (void *) &disabled,
00048   .iov_len = sizeof (disabled)
00049 };
00050 
00051 
00052 /* This is the standard reply in case we haven't found the dataset.  */
00053 static const serv_response_header notfound =
00054 {
00055   .version = NSCD_VERSION,
00056   .found = 0,
00057   .s_name_len = 0,
00058   .s_proto_len = 0,
00059   .s_aliases_cnt = 0,
00060   .s_port = -1
00061 };
00062 
00063 
00064 static void
00065 cache_addserv (struct database_dyn *db, int fd, request_header *req,
00066               const void *key, struct servent *serv, uid_t owner,
00067               struct hashentry *he, struct datahead *dh, int errval)
00068 {
00069   ssize_t total;
00070   ssize_t written;
00071   time_t t = time (NULL);
00072 
00073   /* We allocate all data in one memory block: the iov vector,
00074      the response header and the dataset itself.  */
00075   struct dataset
00076   {
00077     struct datahead head;
00078     serv_response_header resp;
00079     char strdata[0];
00080   } *dataset;
00081 
00082   assert (offsetof (struct dataset, resp) == offsetof (struct datahead, data));
00083 
00084   if (serv == NULL)
00085     {
00086       if (he != NULL && errval == EAGAIN)
00087        {
00088          /* If we have an old record available but cannot find one
00089             now because the service is not available we keep the old
00090             record and make sure it does not get removed.  */
00091          if (reload_count != UINT_MAX)
00092            /* Do not reset the value if we never not reload the record.  */
00093            dh->nreloads = reload_count - 1;
00094 
00095          written = total = 0;
00096        }
00097       else
00098        {
00099          /* We have no data.  This means we send the standard reply for this
00100             case.  */
00101          total = sizeof (notfound);
00102 
00103          written = TEMP_FAILURE_RETRY (send (fd, &notfound, total,
00104                                          MSG_NOSIGNAL));
00105 
00106          dataset = mempool_alloc (db, sizeof (struct dataset) + req->key_len,
00107                                IDX_result_data);
00108          /* If we cannot permanently store the result, so be it.  */
00109          if (dataset != NULL)
00110            {
00111              dataset->head.allocsize = sizeof (struct dataset) + req->key_len;
00112              dataset->head.recsize = total;
00113              dataset->head.notfound = true;
00114              dataset->head.nreloads = 0;
00115              dataset->head.usable = true;
00116 
00117              /* Compute the timeout time.  */
00118              dataset->head.timeout = t + db->negtimeout;
00119 
00120              /* This is the reply.  */
00121              memcpy (&dataset->resp, &notfound, total);
00122 
00123              /* Copy the key data.  */
00124              memcpy (dataset->strdata, key, req->key_len);
00125 
00126              /* If necessary, we also propagate the data to disk.  */
00127              if (db->persistent)
00128               {
00129                 // XXX async OK?
00130                 uintptr_t pval = (uintptr_t) dataset & ~pagesize_m1;
00131                 msync ((void *) pval,
00132                       ((uintptr_t) dataset & pagesize_m1)
00133                       + sizeof (struct dataset) + req->key_len, MS_ASYNC);
00134               }
00135 
00136              /* Now get the lock to safely insert the records.  */
00137              pthread_rwlock_rdlock (&db->lock);
00138 
00139              (void) cache_add (req->type, &dataset->strdata, req->key_len,
00140                             &dataset->head, true, db, owner, he == NULL);
00141 
00142              pthread_rwlock_unlock (&db->lock);
00143 
00144              /* Mark the old entry as obsolete.  */
00145              if (dh != NULL)
00146               dh->usable = false;
00147            }
00148          else
00149            ++db->head->addfailed;
00150        }
00151     }
00152   else
00153     {
00154       /* Determine the I/O structure.  */
00155       size_t s_name_len = strlen (serv->s_name) + 1;
00156       size_t s_proto_len = strlen (serv->s_proto) + 1;
00157       uint32_t *s_aliases_len;
00158       size_t s_aliases_cnt;
00159       char *aliases;
00160       char *cp;
00161       size_t cnt;
00162 
00163       /* Determine the number of aliases.  */
00164       s_aliases_cnt = 0;
00165       for (cnt = 0; serv->s_aliases[cnt] != NULL; ++cnt)
00166        ++s_aliases_cnt;
00167       /* Determine the length of all aliases.  */
00168       s_aliases_len = (uint32_t *) alloca (s_aliases_cnt * sizeof (uint32_t));
00169       total = 0;
00170       for (cnt = 0; cnt < s_aliases_cnt; ++cnt)
00171        {
00172          s_aliases_len[cnt] = strlen (serv->s_aliases[cnt]) + 1;
00173          total += s_aliases_len[cnt];
00174        }
00175 
00176       total += (offsetof (struct dataset, strdata)
00177               + s_name_len
00178               + s_proto_len
00179               + s_aliases_cnt * sizeof (uint32_t));
00180       written = total;
00181 
00182       /* If we refill the cache, first assume the reconrd did not
00183         change.  Allocate memory on the cache since it is likely
00184         discarded anyway.  If it turns out to be necessary to have a
00185         new record we can still allocate real memory.  */
00186       bool alloca_used = false;
00187       dataset = NULL;
00188 
00189       if (he == NULL)
00190        {
00191          dataset = (struct dataset *) mempool_alloc (db,
00192                                                 total + req->key_len,
00193                                                 IDX_result_data);
00194          if (dataset == NULL)
00195            ++db->head->addfailed;
00196        }
00197 
00198       if (dataset == NULL)
00199        {
00200          /* We cannot permanently add the result in the moment.  But
00201             we can provide the result as is.  Store the data in some
00202             temporary memory.  */
00203          dataset = (struct dataset *) alloca (total + req->key_len);
00204 
00205          /* We cannot add this record to the permanent database.  */
00206          alloca_used = true;
00207        }
00208 
00209       dataset->head.allocsize = total + req->key_len;
00210       dataset->head.recsize = total - offsetof (struct dataset, resp);
00211       dataset->head.notfound = false;
00212       dataset->head.nreloads = he == NULL ? 0 : (dh->nreloads + 1);
00213       dataset->head.usable = true;
00214 
00215       /* Compute the timeout time.  */
00216       dataset->head.timeout = t + db->postimeout;
00217 
00218       dataset->resp.version = NSCD_VERSION;
00219       dataset->resp.found = 1;
00220       dataset->resp.s_name_len = s_name_len;
00221       dataset->resp.s_proto_len = s_proto_len;
00222       dataset->resp.s_port = serv->s_port;
00223       dataset->resp.s_aliases_cnt = s_aliases_cnt;
00224 
00225       cp = dataset->strdata;
00226 
00227       cp = mempcpy (cp, serv->s_name, s_name_len);
00228       cp = mempcpy (cp, serv->s_proto, s_proto_len);
00229       cp = mempcpy (cp, s_aliases_len, s_aliases_cnt * sizeof (uint32_t));
00230 
00231       /* Then the aliases.  */
00232       aliases = cp;
00233       for (cnt = 0; cnt < s_aliases_cnt; ++cnt)
00234        cp = mempcpy (cp, serv->s_aliases[cnt], s_aliases_len[cnt]);
00235 
00236       assert (cp
00237              == dataset->strdata + total - offsetof (struct dataset,
00238                                                 strdata));
00239 
00240       char *key_copy = memcpy (cp, key, req->key_len);
00241 
00242       /* Now we can determine whether on refill we have to create a new
00243         record or not.  */
00244       if (he != NULL)
00245        {
00246          assert (fd == -1);
00247 
00248          if (total + req->key_len == dh->allocsize
00249              && total - offsetof (struct dataset, resp) == dh->recsize
00250              && memcmp (&dataset->resp, dh->data,
00251                       dh->allocsize - offsetof (struct dataset, resp)) == 0)
00252            {
00253              /* The data has not changed.  We will just bump the
00254                timeout value.  Note that the new record has been
00255                allocated on the stack and need not be freed.  */
00256              dh->timeout = dataset->head.timeout;
00257              ++dh->nreloads;
00258            }
00259          else
00260            {
00261              /* We have to create a new record.  Just allocate
00262                appropriate memory and copy it.  */
00263              struct dataset *newp
00264               = (struct dataset *) mempool_alloc (db, total + req->key_len,
00265                                               IDX_result_data);
00266              if (newp != NULL)
00267               {
00268                 /* Adjust pointers into the memory block.  */
00269                 aliases = (char *) newp + (aliases - (char *) dataset);
00270                 assert (key_copy != NULL);
00271                 key_copy = (char *) newp + (key_copy - (char *) dataset);
00272 
00273                 dataset = memcpy (newp, dataset, total + req->key_len);
00274                 alloca_used = false;
00275               }
00276              else
00277               ++db->head->addfailed;
00278 
00279              /* Mark the old record as obsolete.  */
00280              dh->usable = false;
00281            }
00282        }
00283       else
00284        {
00285          /* We write the dataset before inserting it to the database
00286             since while inserting this thread might block and so would
00287             unnecessarily keep the receiver waiting.  */
00288          assert (fd != -1);
00289 
00290 #ifdef HAVE_SENDFILE
00291          if (__builtin_expect (db->mmap_used, 1) && !alloca_used)
00292            {
00293              assert (db->wr_fd != -1);
00294              assert ((char *) &dataset->resp > (char *) db->data);
00295              assert ((char *) &dataset->resp - (char *) db->head
00296                     + total
00297                     <= (sizeof (struct database_pers_head)
00298                        + db->head->module * sizeof (ref_t)
00299                        + db->head->data_size));
00300              written = sendfileall (fd, db->wr_fd,
00301                                  (char *) &dataset->resp
00302                                  - (char *) db->head, total);
00303 # ifndef __ASSUME_SENDFILE
00304              if (written == -1 && errno == ENOSYS)
00305               goto use_write;
00306 # endif
00307            }
00308          else
00309 # ifndef __ASSUME_SENDFILE
00310          use_write:
00311 # endif
00312 #endif
00313            written = writeall (fd, &dataset->resp, total);
00314        }
00315 
00316       /* Add the record to the database.  But only if it has not been
00317         stored on the stack.  */
00318       if (! alloca_used)
00319        {
00320          /* If necessary, we also propagate the data to disk.  */
00321          if (db->persistent)
00322            {
00323              // XXX async OK?
00324              uintptr_t pval = (uintptr_t) dataset & ~pagesize_m1;
00325              msync ((void *) pval,
00326                    ((uintptr_t) dataset & pagesize_m1)
00327                    + total + req->key_len, MS_ASYNC);
00328            }
00329 
00330          /* Now get the lock to safely insert the records.  */
00331          pthread_rwlock_rdlock (&db->lock);
00332 
00333          (void) cache_add (req->type, key_copy, req->key_len,
00334                          &dataset->head, true, db, owner, he == NULL);
00335 
00336          pthread_rwlock_unlock (&db->lock);
00337        }
00338     }
00339 
00340   if (__builtin_expect (written != total, 0) && debug_level > 0)
00341     {
00342       char buf[256];
00343       dbg_log (_("short write in %s: %s"),  __FUNCTION__,
00344               strerror_r (errno, buf, sizeof (buf)));
00345     }
00346 }
00347 
00348 
00349 static int
00350 lookup (int type, char *key, struct servent *resultbufp, char *buffer,
00351        size_t buflen, struct servent **serv)
00352 {
00353   char *proto = strrchr (key, '/');
00354   if (proto != NULL && proto != key)
00355     {
00356       key = strndupa (key, proto - key);
00357       if (proto[1] == '\0')
00358        proto = NULL;
00359       else
00360        ++proto;
00361     }
00362 
00363   if (type == GETSERVBYNAME)
00364     return __getservbyname_r (key, proto, resultbufp, buffer, buflen, serv);
00365 
00366   assert (type == GETSERVBYPORT);
00367   return __getservbyport_r (atol (key), proto, resultbufp, buffer, buflen,
00368                          serv);
00369 }
00370 
00371 
00372 static void
00373 addservbyX (struct database_dyn *db, int fd, request_header *req,
00374            char *key, uid_t uid, struct hashentry *he, struct datahead *dh)
00375 {
00376   /* Search for the entry matching the key.  Please note that we don't
00377      look again in the table whether the dataset is now available.  We
00378      simply insert it.  It does not matter if it is in there twice.  The
00379      pruning function only will look at the timestamp.  */
00380   size_t buflen = 1024;
00381   char *buffer = (char *) alloca (buflen);
00382   struct servent resultbuf;
00383   struct servent *serv;
00384   bool use_malloc = false;
00385   int errval = 0;
00386 
00387   if (__builtin_expect (debug_level > 0, 0))
00388     {
00389       if (he == NULL)
00390        dbg_log (_("Haven't found \"%s\" in services cache!"), key);
00391       else
00392        dbg_log (_("Reloading \"%s\" in services cache!"), key);
00393     }
00394 
00395   while (lookup (req->type, key, &resultbuf, buffer, buflen, &serv) != 0
00396         && (errval = errno) == ERANGE)
00397     {
00398       errno = 0;
00399 
00400       if (__builtin_expect (buflen > 32768, 0))
00401        {
00402          char *old_buffer = buffer;
00403          buflen *= 2;
00404          buffer = (char *) realloc (use_malloc ? buffer : NULL, buflen);
00405          if (buffer == NULL)
00406            {
00407              /* We ran out of memory.  We cannot do anything but
00408                sending a negative response.  In reality this should
00409                never happen.  */
00410              serv = NULL;
00411              buffer = old_buffer;
00412 
00413              /* We set the error to indicate this is (possibly) a
00414                temporary error and that it does not mean the entry
00415                is not available at all.  */
00416              errval = EAGAIN;
00417              break;
00418            }
00419          use_malloc = true;
00420        }
00421       else
00422        /* Allocate a new buffer on the stack.  If possible combine it
00423           with the previously allocated buffer.  */
00424        buffer = (char *) extend_alloca (buffer, buflen, 2 * buflen);
00425     }
00426 
00427   cache_addserv (db, fd, req, key, serv, uid, he, dh, errval);
00428 
00429   if (use_malloc)
00430     free (buffer);
00431 }
00432 
00433 
00434 void
00435 addservbyname (struct database_dyn *db, int fd, request_header *req,
00436               void *key, uid_t uid)
00437 {
00438   addservbyX (db, fd, req, key, uid, NULL, NULL);
00439 }
00440 
00441 
00442 void
00443 readdservbyname (struct database_dyn *db, struct hashentry *he,
00444                struct datahead *dh)
00445 {
00446   request_header req =
00447     {
00448       .type = GETSERVBYNAME,
00449       .key_len = he->len
00450     };
00451 
00452   addservbyX (db, -1, &req, db->data + he->key, he->owner, he, dh);
00453 }
00454 
00455 
00456 void
00457 addservbyport (struct database_dyn *db, int fd, request_header *req,
00458               void *key, uid_t uid)
00459 {
00460   addservbyX (db, fd, req, key, uid, NULL, NULL);
00461 }
00462 
00463 
00464 void
00465 readdservbyport (struct database_dyn *db, struct hashentry *he,
00466                struct datahead *dh)
00467 {
00468   request_header req =
00469     {
00470       .type = GETSERVBYPORT,
00471       .key_len = he->len
00472     };
00473 
00474   addservbyX (db, -1, &req, db->data + he->key, he->owner, he, dh);
00475 }