Replace lll_private_futex_* (*) with lll_futex_* (*, LLL_PRIVATE).
[kopensolaris-gnu/glibc.git] / nscd / aicache.c
1 /* Cache handling for host lookup.
2    Copyright (C) 2004, 2005, 2006, 2007 Free Software Foundation, Inc.
3    This file is part of the GNU C Library.
4    Contributed by Ulrich Drepper <drepper@redhat.com>, 2004.
5
6    This program is free software; you can redistribute it and/or modify
7    it under the terms of the GNU General Public License as published
8    by the Free Software Foundation; version 2 of the License, or
9    (at your option) any later version.
10
11    This program is distributed in the hope that it will be useful,
12    but WITHOUT ANY WARRANTY; without even the implied warranty of
13    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14    GNU General Public License for more details.
15
16    You should have received a copy of the GNU General Public License
17    along with this program; if not, write to the Free Software Foundation,
18    Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.  */
19
20 #include <assert.h>
21 #include <errno.h>
22 #include <libintl.h>
23 #include <netdb.h>
24 #include <string.h>
25 #include <time.h>
26 #include <unistd.h>
27 #include <sys/mman.h>
28
29 #include "dbg_log.h"
30 #include "nscd.h"
31 #ifdef HAVE_SENDFILE
32 # include <kernel-features.h>
33 #endif
34
35
36 typedef enum nss_status (*nss_gethostbyname3_r)
37   (const char *name, int af, struct hostent *host,
38    char *buffer, size_t buflen, int *errnop,
39    int *h_errnop, int32_t *, char **);
40 typedef enum nss_status (*nss_getcanonname_r)
41   (const char *name, char *buffer, size_t buflen, char **result,
42    int *errnop, int *h_errnop);
43
44
45 static const ai_response_header notfound =
46 {
47   .version = NSCD_VERSION,
48   .found = 0,
49   .naddrs = 0,
50   .addrslen = 0,
51   .canonlen = 0,
52   .error = 0
53 };
54
55
56 static void
57 addhstaiX (struct database_dyn *db, int fd, request_header *req,
58            void *key, uid_t uid, struct hashentry *he, struct datahead *dh)
59 {
60   /* Search for the entry matching the key.  Please note that we don't
61      look again in the table whether the dataset is now available.  We
62      simply insert it.  It does not matter if it is in there twice.  The
63      pruning function only will look at the timestamp.  */
64
65   /* We allocate all data in one memory block: the iov vector,
66      the response header and the dataset itself.  */
67   struct dataset
68   {
69     struct datahead head;
70     ai_response_header resp;
71     char strdata[0];
72   } *dataset = NULL;
73
74   if (__builtin_expect (debug_level > 0, 0))
75     {
76       if (he == NULL)
77         dbg_log (_("Haven't found \"%s\" in hosts cache!"), (char *) key);
78       else
79         dbg_log (_("Reloading \"%s\" in hosts cache!"), (char *) key);
80     }
81
82   static service_user *hosts_database;
83   service_user *nip = NULL;
84   int no_more;
85   int rc6 = 0;
86   int rc4 = 0;
87   int herrno = 0;
88
89   if (hosts_database != NULL)
90     {
91       nip = hosts_database;
92       no_more = 0;
93     }
94   else
95     no_more = __nss_database_lookup ("hosts", NULL,
96                                      "dns [!UNAVAIL=return] files", &nip);
97
98   if (__res_maybe_init (&_res, 0) == -1)
99             no_more = 1;
100
101   /* If we are looking for both IPv4 and IPv6 address we don't want
102      the lookup functions to automatically promote IPv4 addresses to
103      IPv6 addresses.  Currently this is decided by setting the
104      RES_USE_INET6 bit in _res.options.  */
105   int old_res_options = _res.options;
106   _res.options &= ~RES_USE_INET6;
107
108   size_t tmpbuf6len = 512;
109   char *tmpbuf6 = alloca (tmpbuf6len);
110   size_t tmpbuf4len = 0;
111   char *tmpbuf4 = NULL;
112   char *canon = NULL;
113   int32_t ttl = UINT32_MAX;
114   ssize_t total = 0;
115   char *key_copy = NULL;
116   bool alloca_used = false;
117
118   while (!no_more)
119     {
120       int status[2] = { NSS_STATUS_UNAVAIL, NSS_STATUS_UNAVAIL };
121
122       /* Prefer the function which also returns the TTL and canonical name.  */
123       nss_gethostbyname3_r fct = __nss_lookup_function (nip,
124                                                         "gethostbyname3_r");
125       if (fct == NULL)
126         fct = __nss_lookup_function (nip, "gethostbyname2_r");
127
128       if (fct != NULL)
129         {
130           struct hostent th[2];
131
132           /* Collect IPv6 information first.  */
133           while (1)
134             {
135               rc6 = 0;
136               status[0] = DL_CALL_FCT (fct, (key, AF_INET6, &th[0], tmpbuf6,
137                                              tmpbuf6len, &rc6, &herrno,
138                                              &ttl, &canon));
139               if (rc6 != ERANGE || herrno != NETDB_INTERNAL)
140                 break;
141               tmpbuf6 = extend_alloca (tmpbuf6, tmpbuf6len, 2 * tmpbuf6len);
142             }
143
144           if (rc6 != 0 && herrno == NETDB_INTERNAL)
145             goto out;
146
147           /* If the IPv6 lookup has been successful do not use the
148              buffer used in that lookup, use a new one.  */
149           if (status[0] == NSS_STATUS_SUCCESS && rc6 == 0)
150             {
151               tmpbuf4len = 512;
152               tmpbuf4 = alloca (tmpbuf4len);
153             }
154           else
155             {
156               tmpbuf4len = tmpbuf6len;
157               tmpbuf4 = tmpbuf6;
158             }
159
160           /* Next collect IPv4 information first.  */
161           while (1)
162             {
163               rc4 = 0;
164               status[1] = DL_CALL_FCT (fct, (key, AF_INET, &th[1], tmpbuf4,
165                                              tmpbuf4len, &rc4, &herrno,
166                                              ttl == UINT32_MAX ? &ttl : NULL,
167                                              canon == NULL ? &canon : NULL));
168               if (rc4 != ERANGE || herrno != NETDB_INTERNAL)
169                 break;
170               tmpbuf4 = extend_alloca (tmpbuf4, tmpbuf4len, 2 * tmpbuf4len);
171             }
172
173           if (rc4 != 0 || herrno == NETDB_INTERNAL)
174             goto out;
175
176           if (status[0] == NSS_STATUS_SUCCESS
177               || status[1] == NSS_STATUS_SUCCESS)
178             {
179               /* We found the data.  Count the addresses and the size.  */
180               int naddrs = 0;
181               size_t addrslen = 0;
182               for (int j = 0; j < 2; ++j)
183                 if (status[j] == NSS_STATUS_SUCCESS)
184                   for (int i = 0; th[j].h_addr_list[i] != NULL; ++i)
185                     {
186                       ++naddrs;
187                       addrslen += th[j].h_length;
188                     }
189
190               if (canon == NULL)
191                 {
192                   /* Determine the canonical name.  */
193                   nss_getcanonname_r cfct;
194                   cfct = __nss_lookup_function (nip, "getcanonname_r");
195                   if (cfct != NULL)
196                     {
197                       const size_t max_fqdn_len = 256;
198                       char *buf = alloca (max_fqdn_len);
199                       char *s;
200                       int rc;
201
202                       if (DL_CALL_FCT (cfct, (key, buf, max_fqdn_len, &s, &rc,
203                                               &herrno)) == NSS_STATUS_SUCCESS)
204                         canon = s;
205                       else
206                         /* Set to name now to avoid using gethostbyaddr.  */
207                         canon = key;
208                     }
209                   else
210                     {
211                       struct hostent *he = NULL;
212                       int herrno;
213                       struct hostent he_mem;
214                       void *addr;
215                       size_t addrlen;
216                       int addrfamily;
217
218                       if (status[1] == NSS_STATUS_SUCCESS)
219                         {
220                           addr = th[1].h_addr_list[0];
221                           addrlen = sizeof (struct in_addr);
222                           addrfamily = AF_INET;
223                         }
224                       else
225                         {
226                           addr = th[0].h_addr_list[0];
227                           addrlen = sizeof (struct in6_addr);
228                           addrfamily = AF_INET6;
229                         }
230
231                       size_t tmpbuflen = 512;
232                       char *tmpbuf = alloca (tmpbuflen);
233                       int rc;
234                       while (1)
235                         {
236                           rc = __gethostbyaddr_r (addr, addrlen, addrfamily,
237                                                   &he_mem, tmpbuf, tmpbuflen,
238                                                   &he, &herrno);
239                           if (rc != ERANGE || herrno != NETDB_INTERNAL)
240                             break;
241                           tmpbuf = extend_alloca (tmpbuf, tmpbuflen,
242                                                   tmpbuflen * 2);
243                         }
244
245                       if (rc == 0)
246                         {
247                           if (he != NULL)
248                             canon = he->h_name;
249                           else
250                             canon = key;
251                         }
252                     }
253                 }
254               size_t canonlen = canon == NULL ? 0 : (strlen (canon) + 1);
255
256               total = sizeof (*dataset) + naddrs + addrslen + canonlen;
257
258               /* Now we can allocate the data structure.  If the TTL
259                  of the entry is reported as zero do not cache the
260                  entry at all.  */
261               if (ttl != 0 && he == NULL)
262                 {
263                   dataset = (struct dataset *) mempool_alloc (db,
264                                                               total
265                                                               + req->key_len);
266                   if (dataset == NULL)
267                     ++db->head->addfailed;
268                 }
269
270               if (dataset == NULL)
271                 {
272                   /* We cannot permanently add the result in the moment.  But
273                      we can provide the result as is.  Store the data in some
274                      temporary memory.  */
275                   dataset = (struct dataset *) alloca (total + req->key_len);
276
277                   /* We cannot add this record to the permanent database.  */
278                   alloca_used = true;
279                 }
280
281               dataset->head.allocsize = total + req->key_len;
282               dataset->head.recsize = total - offsetof (struct dataset, resp);
283               dataset->head.notfound = false;
284               dataset->head.nreloads = he == NULL ? 0 : (dh->nreloads + 1);
285               dataset->head.usable = true;
286
287               /* Compute the timeout time.  */
288               dataset->head.timeout = time (NULL) + MIN (db->postimeout, ttl);
289
290               dataset->resp.version = NSCD_VERSION;
291               dataset->resp.found = 1;
292               dataset->resp.naddrs = naddrs;
293               dataset->resp.addrslen = addrslen;
294               dataset->resp.canonlen = canonlen;
295               dataset->resp.error = NETDB_SUCCESS;
296
297               char *addrs = (char *) (&dataset->resp + 1);
298               uint8_t *family = (uint8_t *) (addrs + addrslen);
299
300               for (int j = 0; j < 2; ++j)
301                 if (status[j] == NSS_STATUS_SUCCESS)
302                   for (int i = 0; th[j].h_addr_list[i] != NULL; ++i)
303                     {
304                       addrs = mempcpy (addrs, th[j].h_addr_list[i],
305                                        th[j].h_length);
306                       *family++ = th[j].h_addrtype;
307                     }
308
309               void *cp = family;
310               if (canon != NULL)
311                 cp = mempcpy (cp, canon, canonlen);
312
313               key_copy = memcpy (cp, key, req->key_len);
314
315               /* Now we can determine whether on refill we have to
316                  create a new record or not.  */
317               if (he != NULL)
318                 {
319                   assert (fd == -1);
320
321                   if (total + req->key_len == dh->allocsize
322                       && total - offsetof (struct dataset, resp) == dh->recsize
323                       && memcmp (&dataset->resp, dh->data,
324                                  dh->allocsize
325                                  - offsetof (struct dataset, resp)) == 0)
326                     {
327                       /* The data has not changed.  We will just bump the
328                          timeout value.  Note that the new record has been
329                          allocated on the stack and need not be freed.  */
330                       dh->timeout = dataset->head.timeout;
331                       ++dh->nreloads;
332                     }
333                   else
334                     {
335                       /* We have to create a new record.  Just allocate
336                          appropriate memory and copy it.  */
337                       struct dataset *newp
338                         = (struct dataset *) mempool_alloc (db,
339                                                             total
340                                                             + req->key_len);
341                       if (newp != NULL)
342                         {
343                           /* Adjust pointer into the memory block.  */
344                           key_copy = (char *) newp + (key_copy
345                                                       - (char *) dataset);
346
347                           dataset = memcpy (newp, dataset,
348                                             total + req->key_len);
349                           alloca_used = false;
350                         }
351
352                       /* Mark the old record as obsolete.  */
353                       dh->usable = false;
354                     }
355                 }
356               else
357                 {
358                   /* We write the dataset before inserting it to the
359                      database since while inserting this thread might
360                      block and so would unnecessarily let the receiver
361                      wait.  */
362                   assert (fd != -1);
363
364 #ifdef HAVE_SENDFILE
365                   if (__builtin_expect (db->mmap_used, 1) && !alloca_used)
366                     {
367                       assert (db->wr_fd != -1);
368                       assert ((char *) &dataset->resp > (char *) db->data);
369                       assert ((char *) &dataset->resp - (char *) db->head
370                               + total
371                               <= (sizeof (struct database_pers_head)
372                                   + db->head->module * sizeof (ref_t)
373                                   + db->head->data_size));
374                       ssize_t written;
375                       written = sendfileall (fd, db->wr_fd,
376                                              (char *) &dataset->resp
377                                              - (char *) db->head, total);
378 # ifndef __ASSUME_SENDFILE
379                       if (written == -1 && errno == ENOSYS)
380                         goto use_write;
381 # endif
382                     }
383                   else
384 # ifndef __ASSUME_SENDFILE
385                   use_write:
386 # endif
387 #endif
388                     writeall (fd, &dataset->resp, total);
389                 }
390
391               goto out;
392             }
393
394         }
395
396       if (nss_next_action (nip, status[1]) == NSS_ACTION_RETURN)
397         break;
398
399       if (nip->next == NULL)
400         no_more = -1;
401       else
402         nip = nip->next;
403     }
404
405   /* No result found.  Create a negative result record.  */
406   if (he != NULL && rc4 == EAGAIN)
407     {
408       /* If we have an old record available but cannot find one now
409          because the service is not available we keep the old record
410          and make sure it does not get removed.  */
411       if (reload_count != UINT_MAX && dh->nreloads == reload_count)
412         /* Do not reset the value if we never not reload the record.  */
413         dh->nreloads = reload_count - 1;
414     }
415   else
416     {
417       /* We have no data.  This means we send the standard reply for
418          this case.  */
419       total = sizeof (notfound);
420
421       if (fd != -1)
422         TEMP_FAILURE_RETRY (send (fd, &notfound, total, MSG_NOSIGNAL));
423
424       dataset = mempool_alloc (db, sizeof (struct dataset) + req->key_len);
425       /* If we cannot permanently store the result, so be it.  */
426       if (dataset != NULL)
427         {
428           dataset->head.allocsize = sizeof (struct dataset) + req->key_len;
429           dataset->head.recsize = total;
430           dataset->head.notfound = true;
431           dataset->head.nreloads = 0;
432           dataset->head.usable = true;
433
434           /* Compute the timeout time.  */
435           dataset->head.timeout = time (NULL) + db->negtimeout;
436
437           /* This is the reply.  */
438           memcpy (&dataset->resp, &notfound, total);
439
440           /* Copy the key data.  */
441           key_copy = memcpy (dataset->strdata, key, req->key_len);
442         }
443       else
444         ++db->head->addfailed;
445    }
446
447  out:
448   _res.options = old_res_options;
449
450   if (dataset != NULL && !alloca_used)
451     {
452       /* If necessary, we also propagate the data to disk.  */
453       if (db->persistent)
454         {
455           // XXX async OK?
456           uintptr_t pval = (uintptr_t) dataset & ~pagesize_m1;
457           msync ((void *) pval,
458                  ((uintptr_t) dataset & pagesize_m1) + total + req->key_len,
459                  MS_ASYNC);
460         }
461
462       /* Now get the lock to safely insert the records.  */
463       pthread_rwlock_rdlock (&db->lock);
464
465       if (cache_add (req->type, key_copy, req->key_len, &dataset->head, true,
466                      db, uid) < 0)
467         /* Ensure the data can be recovered.  */
468         dataset->head.usable = false;
469
470       pthread_rwlock_unlock (&db->lock);
471
472       /* Mark the old entry as obsolete.  */
473       if (dh != NULL)
474         dh->usable = false;
475     }
476 }
477
478
479 void
480 addhstai (struct database_dyn *db, int fd, request_header *req, void *key,
481           uid_t uid)
482 {
483   addhstaiX (db, fd, req, key, uid, NULL, NULL);
484 }
485
486
487 void
488 readdhstai (struct database_dyn *db, struct hashentry *he, struct datahead *dh)
489 {
490   request_header req =
491     {
492       .type = GETAI,
493       .key_len = he->len
494     };
495
496   addhstaiX (db, -1, &req, db->data + he->key, he->owner, he, dh);
497 }