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