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