OSDN Git Service

bionic: Sort and cache hosts file data for fast lookup
[android-x86/bionic.git] / libc / dns / net / hosts_cache.c
1 /*
2  * Copyright (C) 2016 The CyanogenMod Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 #include <fcntl.h>
18 #include <netdb.h>
19 #include <stdio.h>
20 #include <stdlib.h>
21 #include <string.h>
22 #include <ctype.h>
23 #include <strings.h>
24 #include <sys/file.h>
25 #include <sys/mman.h>
26 #include <sys/socket.h>
27 #include <sys/stat.h>
28 #include <sys/types.h>
29 #include <unistd.h>
30 #include <utime.h>
31 #include <pthread.h>
32
33 #include <netinet/in6.h>
34 #include <arpa/inet.h>
35
36 #include "hostent.h"
37 #include "resolv_private.h"
38
39 #define MAX_ADDRLEN     (INET6_ADDRSTRLEN - (1 + 5))
40 #define MAX_HOSTLEN     MAXHOSTNAMELEN
41
42 #define ESTIMATED_LINELEN       32
43 #define HCFILE_ALLOC_SIZE       256
44
45 /* From sethostent.c */
46 #define ALIGNBYTES      (sizeof(uintptr_t) - 1)
47 #define ALIGN(p)        (((uintptr_t)(p) + ALIGNBYTES) &~ ALIGNBYTES)
48
49 /*
50  * Host cache entry for hcfile.c_data.
51  * Offsets are into hcfile.h_data.
52  * Strings are *not* terminated by NULL, but by whitespace (isspace) or '#'.
53  * Use hstr* functions with these.
54  */
55 struct hcent
56 {
57         uint32_t        addr;
58         uint32_t        name;
59 };
60
61 /*
62  * Overall host cache file state.
63  */
64 struct hcfile
65 {
66         int             h_fd;
67         struct stat     h_st;
68         char            *h_data;
69
70         uint32_t        c_alloc;
71         uint32_t        c_len;
72         struct hcent    *c_data;
73 };
74 static struct hcfile hcfile;
75 static pthread_mutex_t hclock = PTHREAD_MUTEX_INITIALIZER;
76
77 static size_t hstrlen(const char *s)
78 {
79         const char *p = s;
80         while (*p && *p != '#' && !isspace(*p))
81                 ++p;
82         return p - s;
83 }
84
85 static int hstrcmp(const char *a, const char *b)
86 {
87         size_t alen = hstrlen(a);
88         size_t blen = hstrlen(b);
89         int res = strncmp(a, b, MIN(alen, blen));
90         if (res == 0)
91                 res = alen - blen;
92         return res;
93 }
94
95 static char *hstrcpy(char *dest, const char *src)
96 {
97         size_t len = hstrlen(src);
98         memcpy(dest, src, len);
99         dest[len] = '\0';
100         return dest;
101 }
102
103 static char *hstrdup(const char *s)
104 {
105         size_t len = hstrlen(s);
106         char *dest = (char *)malloc(len + 1);
107         if (!dest)
108                 return NULL;
109         memcpy(dest, s, len);
110         dest[len] = '\0';
111         return dest;
112 }
113
114 static int cmp_hcent_name(const void *a, const void *b)
115 {
116         struct hcent *ea = (struct hcent *)a;
117         const char *na = hcfile.h_data + ea->name;
118         struct hcent *eb = (struct hcent *)b;
119         const char *nb = hcfile.h_data + eb->name;
120
121         return hstrcmp(na, nb);
122 }
123
124 static struct hcent *_hcfindname(const char *name)
125 {
126         size_t first, last, mid;
127         struct hcent *cur = NULL;
128         int cmp;
129
130         if (hcfile.c_len == 0)
131                 return NULL;
132
133         first = 0;
134         last = hcfile.c_len - 1;
135         mid = (first + last) / 2;
136         while (first <= last) {
137                 cur = hcfile.c_data + mid;
138                 cmp = hstrcmp(hcfile.h_data + cur->name, name);
139                 if (cmp == 0)
140                         goto found;
141                 if (cmp < 0)
142                         first = mid + 1;
143                 else {
144                         if (mid > 0)
145                                 last = mid - 1;
146                         else
147                                 return NULL;
148                 }
149                 mid = (first + last) / 2;
150         }
151         return NULL;
152
153 found:
154         while (cur > hcfile.c_data) {
155                 struct hcent *prev = cur - 1;
156                 cmp = cmp_hcent_name(cur, prev);
157                 if (cmp)
158                         break;
159                 cur = prev;
160         }
161
162         return cur;
163 }
164
165 /*
166  * Find next name on line, if any.
167  *
168  * Assumes that line is terminated by LF.
169  */
170 static const char *_hcnextname(const char *name)
171 {
172         while (!isspace(*name)) {
173                 if (*name == '#')
174                         return NULL;
175                 ++name;
176         }
177         while (isspace(*name)) {
178                 if (*name == '\n')
179                         return NULL;
180                 ++name;
181         }
182         if (*name == '#')
183                 return NULL;
184         return name;
185 }
186
187 static int _hcfilemmap(void)
188 {
189         struct stat st;
190         int h_fd;
191         char *h_addr;
192         const char *p, *pend;
193         uint32_t c_alloc;
194
195         h_fd = open(_PATH_HOSTS, O_RDONLY);
196         if (h_fd < 0)
197                 return -1;
198         if (flock(h_fd, LOCK_EX) != 0) {
199                 close(h_fd);
200                 return -1;
201         }
202
203         if (hcfile.h_data) {
204                 memset(&st, 0, sizeof(st));
205                 if (fstat(h_fd, &st) == 0) {
206                         if (st.st_size == hcfile.h_st.st_size &&
207                             st.st_mtime == hcfile.h_st.st_mtime) {
208                                 flock(h_fd, LOCK_UN);
209                                 close(h_fd);
210                                 return 0;
211                         }
212                 }
213                 free(hcfile.c_data);
214                 munmap(hcfile.h_data, hcfile.h_st.st_size);
215                 close(hcfile.h_fd);
216                 memset(&hcfile, 0, sizeof(struct hcfile));
217         }
218
219         if (fstat(h_fd, &st) != 0) {
220                 flock(h_fd, LOCK_UN);
221                 close(h_fd);
222                 return -1;
223         }
224         h_addr = mmap(NULL, st.st_size, PROT_READ, MAP_SHARED, h_fd, 0);
225         if (h_addr == MAP_FAILED) {
226                 flock(h_fd, LOCK_UN);
227                 close(h_fd);
228                 return -1;
229         }
230
231         hcfile.h_fd = h_fd;
232         hcfile.h_st = st;
233         hcfile.h_data = h_addr;
234
235         c_alloc = 0;
236         /*
237          * Do an initial allocation if the file is "large".  Estimate
238          * 32 bytes per line and define "large" as more than half of
239          * the alloc growth size (256 entries).
240          */
241         if (st.st_size >= ESTIMATED_LINELEN * HCFILE_ALLOC_SIZE / 2) {
242                 c_alloc = st.st_size / ESTIMATED_LINELEN;
243                 hcfile.c_data = malloc(c_alloc * sizeof(struct hcent));
244                 if (!hcfile.c_data) {
245                         goto oom;
246                 }
247         }
248
249         p = (const char *)h_addr;
250         pend = p + st.st_size;
251         while (p < pend) {
252                 const char *eol, *addr, *name;
253                 size_t len;
254                 addr = p;
255                 eol = memchr(p, '\n', pend - p);
256                 if (!eol)
257                         break;
258                 p = eol + 1;
259                 if (*addr == '#' || *addr == '\n')
260                         continue;
261                 len = hstrlen(addr);
262                 if (len > MAX_ADDRLEN)
263                         continue;
264                 name = addr + len;
265                 while (name < eol && isspace(*name))
266                         ++name;
267                 while (name < eol) {
268                         len = hstrlen(name);
269                         if (len == 0)
270                                 break;
271                         if (len < MAX_HOSTLEN) {
272                                 struct hcent *ent;
273                                 if (c_alloc <= hcfile.c_len) {
274                                         struct hcent *c_data;
275                                         c_alloc += HCFILE_ALLOC_SIZE;
276                                         c_data = realloc(hcfile.c_data, c_alloc * sizeof(struct hcent));
277                                         if (!c_data) {
278                                                 goto oom;
279                                         }
280                                         hcfile.c_data = c_data;
281                                 }
282                                 ent = hcfile.c_data + hcfile.c_len;
283                                 ent->addr = addr - h_addr;
284                                 ent->name = name - h_addr;
285                                 ++hcfile.c_len;
286                         }
287                         name += len;
288                         while (name < eol && isspace(*name))
289                                 ++name;
290                 }
291         }
292
293         qsort(hcfile.c_data, hcfile.c_len,
294             sizeof(struct hcent), cmp_hcent_name);
295
296         flock(h_fd, LOCK_UN);
297
298         return 0;
299
300 oom:
301         free(hcfile.c_data);
302         munmap(hcfile.h_data, hcfile.h_st.st_size);
303         flock(hcfile.h_fd, LOCK_UN);
304         close(hcfile.h_fd);
305         memset(&hcfile, 0, sizeof(struct hcfile));
306         return -1;
307 }
308
309 /*
310  * Caching version of getaddrinfo.
311  *
312  * If we find the requested host name in the cache, use getaddrinfo to
313  * populate the result for each address we find.
314  *
315  * Note glibc and bionic differ in the handling of ai_canonname.  POSIX
316  * says that ai_canonname is only populated in the first result entry.
317  * glibc does this.  bionic populates ai_canonname in all result entries.
318  * We choose the POSIX/glibc way here.
319  */
320 int hc_getaddrinfo(const char *host, const char *service,
321                    const struct addrinfo *hints,
322                    struct addrinfo **result)
323 {
324         int ret = 0;
325         struct hcent *ent, *cur;
326         struct addrinfo *ai;
327         struct addrinfo rhints;
328         struct addrinfo *last;
329         int canonname = 0;
330         int cmp;
331
332         if (getenv("ANDROID_HOSTS_CACHE_DISABLE") != NULL)
333                 return EAI_SYSTEM;
334
335         /* Avoid needless work and recursion */
336         if (hints && (hints->ai_flags & AI_NUMERICHOST))
337                 return EAI_SYSTEM;
338         if (!host)
339                 return EAI_SYSTEM;
340
341         pthread_mutex_lock(&hclock);
342
343         if (_hcfilemmap() != 0) {
344                 ret = EAI_SYSTEM;
345                 goto out;
346         }
347         ent = _hcfindname(host);
348         if (!ent) {
349                 ret = EAI_NONAME;
350                 goto out;
351         }
352
353         if (hints) {
354                 canonname = (hints->ai_flags & AI_CANONNAME);
355                 memcpy(&rhints, hints, sizeof(rhints));
356                 rhints.ai_flags &= ~AI_CANONNAME;
357         }
358         else {
359                 memset(&rhints, 0, sizeof(rhints));
360         }
361         rhints.ai_flags |= AI_NUMERICHOST;
362
363         last = NULL;
364         cur = ent;
365         do {
366                 char addrstr[MAX_ADDRLEN];
367                 struct addrinfo *res;
368
369                 hstrcpy(addrstr, hcfile.h_data + cur->addr);
370
371                 if (getaddrinfo(addrstr, service, &rhints, &res) == 0) {
372                         if (!last)
373                                 (*result)->ai_next = res;
374                         else
375                                 last->ai_next = res;
376                         last = res;
377                         while (last->ai_next)
378                                 last = last->ai_next;
379                 }
380
381                 if(cur + 1 >= hcfile.c_data + hcfile.c_len)
382                         break;
383                 cmp = cmp_hcent_name(cur, cur + 1);
384                 cur = cur + 1;
385         }
386         while (!cmp);
387
388         if (last == NULL) {
389                 /* This check is equivalent to (*result)->ai_next == NULL */
390                 ret = EAI_NODATA;
391                 goto out;
392         }
393
394         if (canonname) {
395                 ai = (*result)->ai_next;
396                 free(ai->ai_canonname);
397                 ai->ai_canonname = hstrdup(hcfile.h_data + ent->name);
398         }
399
400 out:
401         pthread_mutex_unlock(&hclock);
402         return ret;
403 }
404
405 /*
406  * Caching version of gethtbyname.
407  *
408  * Note glibc and bionic differ in the handling of aliases.  glibc returns
409  * all aliases for all entries, regardless of whether they match h_addrtype.
410  * bionic returns only the aliases for the first hosts entry.  We return all
411  * aliases for all IPv4 entries.
412  *
413  * Additionally, if an alias is IPv6 and the primary name for an alias also
414  * has an IPv4 entry, glibc will return the IPv4 address(es), but bionic
415  * will not.  Neither do we.
416  */
417 int hc_gethtbyname(const char *host, int af, struct getnamaddr *info)
418 {
419         int ret = NETDB_SUCCESS;
420         struct hcent *ent, *cur;
421         int cmp;
422         size_t addrlen;
423         unsigned int naliases = 0;
424         char *aliases[MAXALIASES];
425         unsigned int naddrs = 0;
426         char *addr_ptrs[MAXADDRS];
427         unsigned int n;
428
429         if (getenv("ANDROID_HOSTS_CACHE_DISABLE") != NULL)
430                 return NETDB_INTERNAL;
431
432         switch (af) {
433         case AF_INET:  addrlen = NS_INADDRSZ;  break;
434         case AF_INET6: addrlen = NS_IN6ADDRSZ; break;
435         default:
436                 return NETDB_INTERNAL;
437         }
438
439         pthread_mutex_lock(&hclock);
440
441         if (_hcfilemmap() != 0) {
442                 ret = NETDB_INTERNAL;
443                 goto out;
444         }
445
446         ent = _hcfindname(host);
447         if (!ent) {
448                 ret = HOST_NOT_FOUND;
449                 goto out;
450         }
451
452         cur = ent;
453         do {
454                 char addr[16];
455                 char addrstr[MAX_ADDRLEN];
456                 char namestr[MAX_HOSTLEN];
457                 const char *name;
458
459                 hstrcpy(addrstr, hcfile.h_data + cur->addr);
460                 if (inet_pton(af, addrstr, &addr) == 1) {
461                         char *aligned;
462                         /* First match is considered the official hostname */
463                         if (naddrs == 0) {
464                                 hstrcpy(namestr, hcfile.h_data + cur->name);
465                                 HENT_SCOPY(info->hp->h_name, namestr, info->buf, info->buflen);
466                         }
467                         for (name = hcfile.h_data + cur->name; name; name = _hcnextname(name)) {
468                                 if (!hstrcmp(name, host))
469                                         continue;
470                                 hstrcpy(namestr, name);
471                                 HENT_SCOPY(aliases[naliases], namestr, info->buf, info->buflen);
472                                 ++naliases;
473                                 if (naliases >= MAXALIASES)
474                                         goto nospc;
475                         }
476                         aligned = (char *)ALIGN(info->buf);
477                         if (info->buf != aligned) {
478                                 if ((ptrdiff_t)info->buflen < (aligned - info->buf))
479                                         goto nospc;
480                                 info->buflen -= (aligned - info->buf);
481                                 info->buf = aligned;
482                         }
483                         HENT_COPY(addr_ptrs[naddrs], addr, addrlen, info->buf, info->buflen);
484                         ++naddrs;
485                         if (naddrs >= MAXADDRS)
486                                 goto nospc;
487                 }
488
489                 if(cur + 1 >= hcfile.c_data + hcfile.c_len)
490                         break;
491                 cmp = cmp_hcent_name(cur, cur + 1);
492                 cur = cur + 1;
493         }
494         while (!cmp);
495
496         if (naddrs == 0) {
497                 ret = HOST_NOT_FOUND;
498                 goto out;
499         }
500
501         addr_ptrs[naddrs++] = NULL;
502         aliases[naliases++] = NULL;
503
504         /* hp->h_name already populated */
505         HENT_ARRAY(info->hp->h_aliases, naliases, info->buf, info->buflen);
506         for (n = 0; n < naliases; ++n) {
507                 info->hp->h_aliases[n] = aliases[n];
508         }
509         info->hp->h_addrtype = af;
510         info->hp->h_length = addrlen;
511         HENT_ARRAY(info->hp->h_addr_list, naddrs, info->buf, info->buflen);
512         for (n = 0; n < naddrs; ++n) {
513                 info->hp->h_addr_list[n] = addr_ptrs[n];
514         }
515
516 out:
517         pthread_mutex_unlock(&hclock);
518         *info->he = ret;
519         return ret;
520
521 nospc:
522         ret = NETDB_INTERNAL;
523         goto out;
524 }