Back to index

glibc  2.9
aicache.c
Go to the documentation of this file.
00001 /* Cache handling for host lookup.
00002    Copyright (C) 2004, 2005, 2006, 2007, 2008 Free Software Foundation, Inc.
00003    This file is part of the GNU C Library.
00004    Contributed by Ulrich Drepper <drepper@redhat.com>, 2004.
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 <assert.h>
00021 #include <errno.h>
00022 #include <libintl.h>
00023 #include <netdb.h>
00024 #include <nss.h>
00025 #include <string.h>
00026 #include <time.h>
00027 #include <unistd.h>
00028 #include <sys/mman.h>
00029 
00030 #include "dbg_log.h"
00031 #include "nscd.h"
00032 #ifdef HAVE_SENDFILE
00033 # include <kernel-features.h>
00034 #endif
00035 
00036 
00037 typedef enum nss_status (*nss_gethostbyname4_r)
00038   (const char *name, struct gaih_addrtuple **pat,
00039    char *buffer, size_t buflen, int *errnop,
00040    int *h_errnop, int32_t *ttlp);
00041 typedef enum nss_status (*nss_gethostbyname3_r)
00042   (const char *name, int af, struct hostent *host,
00043    char *buffer, size_t buflen, int *errnop,
00044    int *h_errnop, int32_t *, char **);
00045 typedef enum nss_status (*nss_getcanonname_r)
00046   (const char *name, char *buffer, size_t buflen, char **result,
00047    int *errnop, int *h_errnop);
00048 
00049 
00050 static const ai_response_header notfound =
00051 {
00052   .version = NSCD_VERSION,
00053   .found = 0,
00054   .naddrs = 0,
00055   .addrslen = 0,
00056   .canonlen = 0,
00057   .error = 0
00058 };
00059 
00060 
00061 static void
00062 addhstaiX (struct database_dyn *db, int fd, request_header *req,
00063           void *key, uid_t uid, struct hashentry *he, struct datahead *dh)
00064 {
00065   /* Search for the entry matching the key.  Please note that we don't
00066      look again in the table whether the dataset is now available.  We
00067      simply insert it.  It does not matter if it is in there twice.  The
00068      pruning function only will look at the timestamp.  */
00069 
00070   /* We allocate all data in one memory block: the iov vector,
00071      the response header and the dataset itself.  */
00072   struct dataset
00073   {
00074     struct datahead head;
00075     ai_response_header resp;
00076     char strdata[0];
00077   } *dataset = NULL;
00078 
00079   if (__builtin_expect (debug_level > 0, 0))
00080     {
00081       if (he == NULL)
00082        dbg_log (_("Haven't found \"%s\" in hosts cache!"), (char *) key);
00083       else
00084        dbg_log (_("Reloading \"%s\" in hosts cache!"), (char *) key);
00085     }
00086 
00087   static service_user *hosts_database;
00088   service_user *nip = NULL;
00089   int no_more;
00090   int rc6 = 0;
00091   int rc4 = 0;
00092   int herrno = 0;
00093 
00094   if (hosts_database != NULL)
00095     {
00096       nip = hosts_database;
00097       no_more = 0;
00098     }
00099   else
00100     no_more = __nss_database_lookup ("hosts", NULL,
00101                                  "dns [!UNAVAIL=return] files", &nip);
00102 
00103   if (__res_maybe_init (&_res, 0) == -1)
00104            no_more = 1;
00105 
00106   /* If we are looking for both IPv4 and IPv6 address we don't want
00107      the lookup functions to automatically promote IPv4 addresses to
00108      IPv6 addresses.  Currently this is decided by setting the
00109      RES_USE_INET6 bit in _res.options.  */
00110   int old_res_options = _res.options;
00111   _res.options &= ~RES_USE_INET6;
00112 
00113   size_t tmpbuf6len = 512;
00114   char *tmpbuf6 = alloca (tmpbuf6len);
00115   size_t tmpbuf4len = 0;
00116   char *tmpbuf4 = NULL;
00117   int32_t ttl = INT32_MAX;
00118   ssize_t total = 0;
00119   char *key_copy = NULL;
00120   bool alloca_used = false;
00121 
00122   while (!no_more)
00123     {
00124       void *cp;
00125       int status[2] = { NSS_STATUS_UNAVAIL, NSS_STATUS_UNAVAIL };
00126       int naddrs = 0;
00127       size_t addrslen = 0;
00128       char *canon = NULL;
00129       size_t canonlen;
00130 
00131       nss_gethostbyname4_r fct4 = __nss_lookup_function (nip,
00132                                                   "gethostbyname4_r");
00133       if (fct4 != NULL)
00134        {
00135          struct gaih_addrtuple *at = NULL;
00136          while (1)
00137            {
00138              rc6 = 0;
00139              herrno = 0;
00140              status[1] = DL_CALL_FCT (fct4, (key, &at, tmpbuf6, tmpbuf6len,
00141                                          &rc6, &herrno, &ttl));
00142              if (rc6 != ERANGE || (herrno != NETDB_INTERNAL
00143                                 && herrno != TRY_AGAIN))
00144               break;
00145              tmpbuf6 = extend_alloca (tmpbuf6, tmpbuf6len, 2 * tmpbuf6len);
00146            }
00147 
00148          if (rc6 != 0 && herrno == NETDB_INTERNAL)
00149            goto out;
00150 
00151          if (status[1] != NSS_STATUS_SUCCESS)
00152            goto next_nip;
00153 
00154          /* We found the data.  Count the addresses and the size.  */
00155          for (const struct gaih_addrtuple *at2 = at; at2 != NULL;
00156               at2 = at2->next)
00157            {
00158              ++naddrs;
00159              /* We do not handle anything other than IPv4 and IPv6
00160                addresses.  The getaddrinfo implementation does not
00161                either so it is not worth trying to do more.  */
00162              if (at2->family == AF_INET)
00163               addrslen += INADDRSZ;
00164              else if (at2->family == AF_INET6)
00165               addrslen += IN6ADDRSZ;
00166            }
00167          canon = at->name;
00168          canonlen = strlen (canon) + 1;
00169 
00170          total = sizeof (*dataset) + naddrs + addrslen + canonlen;
00171 
00172          /* Now we can allocate the data structure.  If the TTL of the
00173             entry is reported as zero do not cache the entry at all.  */
00174          if (ttl != 0 && he == NULL)
00175            {
00176              dataset = (struct dataset *) mempool_alloc (db, total
00177                                                    + req->key_len,
00178                                                    IDX_result_data);
00179              if (dataset == NULL)
00180               ++db->head->addfailed;
00181            }
00182 
00183          if (dataset == NULL)
00184            {
00185              /* We cannot permanently add the result in the moment.  But
00186                we can provide the result as is.  Store the data in some
00187                temporary memory.  */
00188              dataset = (struct dataset *) alloca (total + req->key_len);
00189 
00190              /* We cannot add this record to the permanent database.  */
00191              alloca_used = true;
00192            }
00193 
00194          /* Fill in the address and address families.  */
00195          char *addrs = dataset->strdata;
00196          uint8_t *family = (uint8_t *) (addrs + addrslen);
00197 
00198          for (const struct gaih_addrtuple *at2 = at; at2 != NULL;
00199               at2 = at2->next)
00200            {
00201              *family++ = at2->family;
00202              if (at2->family == AF_INET)
00203               addrs = mempcpy (addrs, at2->addr, INADDRSZ);
00204              else if (at2->family == AF_INET6)
00205               addrs = mempcpy (addrs, at2->addr, IN6ADDRSZ);
00206            }
00207 
00208          cp = family;
00209        }
00210       else
00211        {
00212          /* Prefer the function which also returns the TTL and
00213             canonical name.  */
00214          nss_gethostbyname3_r fct = __nss_lookup_function (nip,
00215                                                      "gethostbyname3_r");
00216          if (fct == NULL)
00217            fct = __nss_lookup_function (nip, "gethostbyname2_r");
00218 
00219          if (fct == NULL)
00220            goto next_nip;
00221 
00222          struct hostent th[2];
00223 
00224          /* Collect IPv6 information first.  */
00225          while (1)
00226            {
00227              rc6 = 0;
00228              status[0] = DL_CALL_FCT (fct, (key, AF_INET6, &th[0], tmpbuf6,
00229                                         tmpbuf6len, &rc6, &herrno, &ttl,
00230                                         &canon));
00231              if (rc6 != ERANGE || herrno != NETDB_INTERNAL)
00232               break;
00233              tmpbuf6 = extend_alloca (tmpbuf6, tmpbuf6len, 2 * tmpbuf6len);
00234            }
00235 
00236          if (rc6 != 0 && herrno == NETDB_INTERNAL)
00237            goto out;
00238 
00239          /* If the IPv6 lookup has been successful do not use the
00240             buffer used in that lookup, use a new one.  */
00241          if (status[0] == NSS_STATUS_SUCCESS && rc6 == 0)
00242            {
00243              tmpbuf4len = 512;
00244              tmpbuf4 = alloca (tmpbuf4len);
00245            }
00246          else
00247            {
00248              tmpbuf4len = tmpbuf6len;
00249              tmpbuf4 = tmpbuf6;
00250            }
00251 
00252          /* Next collect IPv4 information.  */
00253          while (1)
00254            {
00255              rc4 = 0;
00256              status[1] = DL_CALL_FCT (fct, (key, AF_INET, &th[1], tmpbuf4,
00257                                         tmpbuf4len, &rc4, &herrno,
00258                                         ttl == INT32_MAX ? &ttl : NULL,
00259                                         canon == NULL ? &canon : NULL));
00260              if (rc4 != ERANGE || herrno != NETDB_INTERNAL)
00261               break;
00262              tmpbuf4 = extend_alloca (tmpbuf4, tmpbuf4len, 2 * tmpbuf4len);
00263            }
00264 
00265          if (rc4 != 0 && herrno == NETDB_INTERNAL)
00266            goto out;
00267 
00268          if (status[0] != NSS_STATUS_SUCCESS
00269              && status[1] != NSS_STATUS_SUCCESS)
00270            goto next_nip;
00271 
00272          /* We found the data.  Count the addresses and the size.  */
00273          for (int j = 0; j < 2; ++j)
00274            if (status[j] == NSS_STATUS_SUCCESS)
00275              for (int i = 0; th[j].h_addr_list[i] != NULL; ++i)
00276               {
00277                 ++naddrs;
00278                 addrslen += th[j].h_length;
00279               }
00280 
00281          if (canon == NULL)
00282            {
00283              /* Determine the canonical name.  */
00284              nss_getcanonname_r cfct;
00285              cfct = __nss_lookup_function (nip, "getcanonname_r");
00286              if (cfct != NULL)
00287               {
00288                 const size_t max_fqdn_len = 256;
00289                 char *buf = alloca (max_fqdn_len);
00290                 char *s;
00291                 int rc;
00292 
00293                 if (DL_CALL_FCT (cfct, (key, buf, max_fqdn_len, &s,
00294                                      &rc, &herrno))
00295                     == NSS_STATUS_SUCCESS)
00296                   canon = s;
00297                 else
00298                   /* Set to name now to avoid using gethostbyaddr.  */
00299                   canon = key;
00300               }
00301              else
00302               {
00303                 struct hostent *he = NULL;
00304                 int herrno;
00305                 struct hostent he_mem;
00306                 void *addr;
00307                 size_t addrlen;
00308                 int addrfamily;
00309 
00310                 if (status[1] == NSS_STATUS_SUCCESS)
00311                   {
00312                     addr = th[1].h_addr_list[0];
00313                     addrlen = sizeof (struct in_addr);
00314                     addrfamily = AF_INET;
00315                   }
00316                 else
00317                   {
00318                     addr = th[0].h_addr_list[0];
00319                     addrlen = sizeof (struct in6_addr);
00320                     addrfamily = AF_INET6;
00321                   }
00322 
00323                 size_t tmpbuflen = 512;
00324                 char *tmpbuf = alloca (tmpbuflen);
00325                 int rc;
00326                 while (1)
00327                   {
00328                     rc = __gethostbyaddr2_r (addr, addrlen, addrfamily,
00329                                           &he_mem, tmpbuf, tmpbuflen,
00330                                           &he, &herrno, NULL);
00331                     if (rc != ERANGE || herrno != NETDB_INTERNAL)
00332                      break;
00333                     tmpbuf = extend_alloca (tmpbuf, tmpbuflen,
00334                                          tmpbuflen * 2);
00335                   }
00336 
00337                 if (rc == 0)
00338                   {
00339                     if (he != NULL)
00340                      canon = he->h_name;
00341                     else
00342                      canon = key;
00343                   }
00344               }
00345            }
00346 
00347          canonlen = canon == NULL ? 0 : (strlen (canon) + 1);
00348 
00349          total = sizeof (*dataset) + naddrs + addrslen + canonlen;
00350 
00351 
00352          /* Now we can allocate the data structure.  If the TTL of the
00353             entry is reported as zero do not cache the entry at all.  */
00354          if (ttl != 0 && he == NULL)
00355            {
00356              dataset = (struct dataset *) mempool_alloc (db, total
00357                                                    + req->key_len,
00358                                                    IDX_result_data);
00359              if (dataset == NULL)
00360               ++db->head->addfailed;
00361            }
00362 
00363          if (dataset == NULL)
00364            {
00365              /* We cannot permanently add the result in the moment.  But
00366                we can provide the result as is.  Store the data in some
00367                temporary memory.  */
00368              dataset = (struct dataset *) alloca (total + req->key_len);
00369 
00370              /* We cannot add this record to the permanent database.  */
00371              alloca_used = true;
00372            }
00373 
00374          /* Fill in the address and address families.  */
00375          char *addrs = dataset->strdata;
00376          uint8_t *family = (uint8_t *) (addrs + addrslen);
00377 
00378          for (int j = 0; j < 2; ++j)
00379            if (status[j] == NSS_STATUS_SUCCESS)
00380              for (int i = 0; th[j].h_addr_list[i] != NULL; ++i)
00381               {
00382                 addrs = mempcpy (addrs, th[j].h_addr_list[i],
00383                                th[j].h_length);
00384                 *family++ = th[j].h_addrtype;
00385               }
00386 
00387          cp = family;
00388        }
00389 
00390       /* Fill in the rest of the dataset.  */
00391       dataset->head.allocsize = total + req->key_len;
00392       dataset->head.recsize = total - offsetof (struct dataset, resp);
00393       dataset->head.notfound = false;
00394       dataset->head.nreloads = he == NULL ? 0 : (dh->nreloads + 1);
00395       dataset->head.usable = true;
00396 
00397       /* Compute the timeout time.  */
00398       dataset->head.timeout = time (NULL) + (ttl == INT32_MAX
00399                                         ? db->postimeout : ttl);
00400 
00401       dataset->resp.version = NSCD_VERSION;
00402       dataset->resp.found = 1;
00403       dataset->resp.naddrs = naddrs;
00404       dataset->resp.addrslen = addrslen;
00405       dataset->resp.canonlen = canonlen;
00406       dataset->resp.error = NETDB_SUCCESS;
00407 
00408       if (canon != NULL)
00409        cp = mempcpy (cp, canon, canonlen);
00410 
00411       key_copy = memcpy (cp, key, req->key_len);
00412 
00413       assert (cp == (char *) dataset + total);
00414 
00415       /* Now we can determine whether on refill we have to create a
00416         new record or not.  */
00417       if (he != NULL)
00418        {
00419          assert (fd == -1);
00420 
00421          if (total + req->key_len == dh->allocsize
00422              && total - offsetof (struct dataset, resp) == dh->recsize
00423              && memcmp (&dataset->resp, dh->data,
00424                       dh->allocsize - offsetof (struct dataset,
00425                                              resp)) == 0)
00426            {
00427              /* The data has not changed.  We will just bump the
00428                timeout value.  Note that the new record has been
00429                allocated on the stack and need not be freed.  */
00430              dh->timeout = dataset->head.timeout;
00431              ++dh->nreloads;
00432            }
00433          else
00434            {
00435              /* We have to create a new record.  Just allocate
00436                appropriate memory and copy it.  */
00437              struct dataset *newp
00438               = (struct dataset *) mempool_alloc (db, total + req->key_len,
00439                                               IDX_result_data);
00440              if (__builtin_expect (newp != NULL, 1))
00441               {
00442                 /* Adjust pointer into the memory block.  */
00443                 key_copy = (char *) newp + (key_copy - (char *) dataset);
00444 
00445                 dataset = memcpy (newp, dataset, total + req->key_len);
00446                 alloca_used = false;
00447               }
00448              else
00449               ++db->head->addfailed;
00450 
00451              /* Mark the old record as obsolete.  */
00452              dh->usable = false;
00453            }
00454        }
00455       else
00456        {
00457          /* We write the dataset before inserting it to the database
00458             since while inserting this thread might block and so
00459             would unnecessarily let the receiver wait.  */
00460          assert (fd != -1);
00461 
00462 #ifdef HAVE_SENDFILE
00463          if (__builtin_expect (db->mmap_used, 1) && !alloca_used)
00464            {
00465              assert (db->wr_fd != -1);
00466              assert ((char *) &dataset->resp > (char *) db->data);
00467              assert ((char *) &dataset->resp - (char *) db->head + total
00468                     <= (sizeof (struct database_pers_head)
00469                        + db->head->module * sizeof (ref_t)
00470                        + db->head->data_size));
00471              ssize_t written;
00472              written = sendfileall (fd, db->wr_fd, (char *) &dataset->resp
00473                                  - (char *) db->head, total);
00474 # ifndef __ASSUME_SENDFILE
00475              if (written == -1 && errno == ENOSYS)
00476               goto use_write;
00477 # endif
00478            }
00479          else
00480 # ifndef __ASSUME_SENDFILE
00481          use_write:
00482 # endif
00483 #endif
00484            writeall (fd, &dataset->resp, total);
00485        }
00486 
00487       goto out;
00488 
00489 next_nip:
00490       if (nss_next_action (nip, status[1]) == NSS_ACTION_RETURN)
00491        break;
00492 
00493       if (nip->next == NULL)
00494        no_more = -1;
00495       else
00496        nip = nip->next;
00497     }
00498 
00499   /* No result found.  Create a negative result record.  */
00500   if (he != NULL && rc4 == EAGAIN)
00501     {
00502       /* If we have an old record available but cannot find one now
00503         because the service is not available we keep the old record
00504         and make sure it does not get removed.  */
00505       if (reload_count != UINT_MAX && dh->nreloads == reload_count)
00506        /* Do not reset the value if we never not reload the record.  */
00507        dh->nreloads = reload_count - 1;
00508     }
00509   else
00510     {
00511       /* We have no data.  This means we send the standard reply for
00512         this case.  */
00513       total = sizeof (notfound);
00514 
00515       if (fd != -1)
00516        TEMP_FAILURE_RETRY (send (fd, &notfound, total, MSG_NOSIGNAL));
00517 
00518       dataset = mempool_alloc (db, sizeof (struct dataset) + req->key_len,
00519                             IDX_result_data);
00520       /* If we cannot permanently store the result, so be it.  */
00521       if (dataset != NULL)
00522        {
00523          dataset->head.allocsize = sizeof (struct dataset) + req->key_len;
00524          dataset->head.recsize = total;
00525          dataset->head.notfound = true;
00526          dataset->head.nreloads = 0;
00527          dataset->head.usable = true;
00528 
00529          /* Compute the timeout time.  */
00530          dataset->head.timeout = time (NULL) + db->negtimeout;
00531 
00532          /* This is the reply.  */
00533          memcpy (&dataset->resp, &notfound, total);
00534 
00535          /* Copy the key data.  */
00536          key_copy = memcpy (dataset->strdata, key, req->key_len);
00537        }
00538       else
00539        ++db->head->addfailed;
00540    }
00541 
00542  out:
00543   _res.options = old_res_options;
00544 
00545   if (dataset != NULL && !alloca_used)
00546     {
00547       /* If necessary, we also propagate the data to disk.  */
00548       if (db->persistent)
00549        {
00550          // XXX async OK?
00551          uintptr_t pval = (uintptr_t) dataset & ~pagesize_m1;
00552          msync ((void *) pval,
00553                ((uintptr_t) dataset & pagesize_m1) + total + req->key_len,
00554                MS_ASYNC);
00555        }
00556 
00557       /* Now get the lock to safely insert the records.  */
00558       pthread_rwlock_rdlock (&db->lock);
00559 
00560       (void) cache_add (req->type, key_copy, req->key_len, &dataset->head,
00561                      true, db, uid, he == NULL);
00562 
00563       pthread_rwlock_unlock (&db->lock);
00564 
00565       /* Mark the old entry as obsolete.  */
00566       if (dh != NULL)
00567        dh->usable = false;
00568     }
00569 }
00570 
00571 
00572 void
00573 addhstai (struct database_dyn *db, int fd, request_header *req, void *key,
00574          uid_t uid)
00575 {
00576   addhstaiX (db, fd, req, key, uid, NULL, NULL);
00577 }
00578 
00579 
00580 void
00581 readdhstai (struct database_dyn *db, struct hashentry *he, struct datahead *dh)
00582 {
00583   request_header req =
00584     {
00585       .type = GETAI,
00586       .key_len = he->len
00587     };
00588 
00589   addhstaiX (db, -1, &req, db->data + he->key, he->owner, he, dh);
00590 }