diff options
Diffstat (limited to 'REORG.TODO/nscd/aicache.c')
-rw-r--r-- | REORG.TODO/nscd/aicache.c | 585 |
1 files changed, 585 insertions, 0 deletions
diff --git a/REORG.TODO/nscd/aicache.c b/REORG.TODO/nscd/aicache.c new file mode 100644 index 0000000000..7bf4131979 --- /dev/null +++ b/REORG.TODO/nscd/aicache.c @@ -0,0 +1,585 @@ +/* Cache handling for host lookup. + Copyright (C) 2004-2017 Free Software Foundation, Inc. + This file is part of the GNU C Library. + Contributed by Ulrich Drepper <drepper@redhat.com>, 2004. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published + by the Free Software Foundation; version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see <http://www.gnu.org/licenses/>. */ + +#include <assert.h> +#include <errno.h> +#include <libintl.h> +#include <netdb.h> +#include <nss.h> +#include <string.h> +#include <time.h> +#include <unistd.h> +#include <sys/mman.h> +#include <resolv/resolv-internal.h> +#include <resolv/res_hconf.h> + +#include "dbg_log.h" +#include "nscd.h" +#ifdef HAVE_SENDFILE +# include <kernel-features.h> +#endif + + +typedef enum nss_status (*nss_gethostbyname4_r) + (const char *name, struct gaih_addrtuple **pat, + char *buffer, size_t buflen, int *errnop, + int *h_errnop, int32_t *ttlp); +typedef enum nss_status (*nss_gethostbyname3_r) + (const char *name, int af, struct hostent *host, + char *buffer, size_t buflen, int *errnop, + int *h_errnop, int32_t *, char **); +typedef enum nss_status (*nss_getcanonname_r) + (const char *name, char *buffer, size_t buflen, char **result, + int *errnop, int *h_errnop); + + +static const ai_response_header notfound = +{ + .version = NSCD_VERSION, + .found = 0, + .naddrs = 0, + .addrslen = 0, + .canonlen = 0, + .error = 0 +}; + + +static time_t +addhstaiX (struct database_dyn *db, int fd, request_header *req, + void *key, uid_t uid, struct hashentry *const he, + struct datahead *dh) +{ + /* Search for the entry matching the key. Please note that we don't + look again in the table whether the dataset is now available. We + simply insert it. It does not matter if it is in there twice. The + pruning function only will look at the timestamp. */ + + /* We allocate all data in one memory block: the iov vector, + the response header and the dataset itself. */ + struct dataset + { + struct datahead head; + ai_response_header resp; + char strdata[0]; + } *dataset = NULL; + + if (__glibc_unlikely (debug_level > 0)) + { + if (he == NULL) + dbg_log (_("Haven't found \"%s\" in hosts cache!"), (char *) key); + else + dbg_log (_("Reloading \"%s\" in hosts cache!"), (char *) key); + } + + static service_user *hosts_database; + service_user *nip; + int no_more; + int rc6 = 0; + int rc4 = 0; + int herrno = 0; + + if (hosts_database == NULL) + no_more = __nss_database_lookup ("hosts", NULL, + "dns [!UNAVAIL=return] files", + &hosts_database); + else + no_more = 0; + nip = hosts_database; + + /* Initialize configurations. */ + _res_hconf_init (); + if (__res_maybe_init (&_res, 0) == -1) + no_more = 1; + + /* If we are looking for both IPv4 and IPv6 address we don't want + the lookup functions to automatically promote IPv4 addresses to + IPv6 addresses. Currently this is decided by setting the + RES_USE_INET6 bit in _res.options. */ + int old_res_options = _res.options; + _res.options &= ~DEPRECATED_RES_USE_INET6; + + size_t tmpbuf6len = 1024; + char *tmpbuf6 = alloca (tmpbuf6len); + size_t tmpbuf4len = 0; + char *tmpbuf4 = NULL; + int32_t ttl = INT32_MAX; + ssize_t total = 0; + char *key_copy = NULL; + bool alloca_used = false; + time_t timeout = MAX_TIMEOUT_VALUE; + + while (!no_more) + { + void *cp; + int status[2] = { NSS_STATUS_UNAVAIL, NSS_STATUS_UNAVAIL }; + int naddrs = 0; + size_t addrslen = 0; + char *canon = NULL; + size_t canonlen; + + nss_gethostbyname4_r fct4 = __nss_lookup_function (nip, + "gethostbyname4_r"); + if (fct4 != NULL) + { + struct gaih_addrtuple atmem; + struct gaih_addrtuple *at; + while (1) + { + at = &atmem; + rc6 = 0; + herrno = 0; + status[1] = DL_CALL_FCT (fct4, (key, &at, tmpbuf6, tmpbuf6len, + &rc6, &herrno, &ttl)); + if (rc6 != ERANGE || (herrno != NETDB_INTERNAL + && herrno != TRY_AGAIN)) + break; + tmpbuf6 = extend_alloca (tmpbuf6, tmpbuf6len, 2 * tmpbuf6len); + } + + if (rc6 != 0 && herrno == NETDB_INTERNAL) + goto out; + + if (status[1] != NSS_STATUS_SUCCESS) + goto next_nip; + + /* We found the data. Count the addresses and the size. */ + for (const struct gaih_addrtuple *at2 = at = &atmem; at2 != NULL; + at2 = at2->next) + { + ++naddrs; + /* We do not handle anything other than IPv4 and IPv6 + addresses. The getaddrinfo implementation does not + either so it is not worth trying to do more. */ + if (at2->family == AF_INET) + addrslen += INADDRSZ; + else if (at2->family == AF_INET6) + addrslen += IN6ADDRSZ; + } + canon = at->name; + canonlen = strlen (canon) + 1; + + total = sizeof (*dataset) + naddrs + addrslen + canonlen; + + /* Now we can allocate the data structure. If the TTL of the + entry is reported as zero do not cache the entry at all. */ + if (ttl != 0 && he == NULL) + dataset = (struct dataset *) mempool_alloc (db, total + + req->key_len, 1); + + if (dataset == NULL) + { + /* We cannot permanently add the result in the moment. But + we can provide the result as is. Store the data in some + temporary memory. */ + dataset = (struct dataset *) alloca (total + req->key_len); + + /* We cannot add this record to the permanent database. */ + alloca_used = true; + } + + /* Fill in the address and address families. */ + char *addrs = dataset->strdata; + uint8_t *family = (uint8_t *) (addrs + addrslen); + + for (const struct gaih_addrtuple *at2 = at; at2 != NULL; + at2 = at2->next) + { + *family++ = at2->family; + if (at2->family == AF_INET) + addrs = mempcpy (addrs, at2->addr, INADDRSZ); + else if (at2->family == AF_INET6) + addrs = mempcpy (addrs, at2->addr, IN6ADDRSZ); + } + + cp = family; + } + else + { + /* Prefer the function which also returns the TTL and + canonical name. */ + nss_gethostbyname3_r fct = __nss_lookup_function (nip, + "gethostbyname3_r"); + if (fct == NULL) + fct = __nss_lookup_function (nip, "gethostbyname2_r"); + + if (fct == NULL) + goto next_nip; + + struct hostent th[2]; + + /* Collect IPv6 information first. */ + while (1) + { + rc6 = 0; + status[0] = DL_CALL_FCT (fct, (key, AF_INET6, &th[0], tmpbuf6, + tmpbuf6len, &rc6, &herrno, &ttl, + &canon)); + if (rc6 != ERANGE || herrno != NETDB_INTERNAL) + break; + tmpbuf6 = extend_alloca (tmpbuf6, tmpbuf6len, 2 * tmpbuf6len); + } + + if (rc6 != 0 && herrno == NETDB_INTERNAL) + goto out; + + /* If the IPv6 lookup has been successful do not use the + buffer used in that lookup, use a new one. */ + if (status[0] == NSS_STATUS_SUCCESS && rc6 == 0) + { + tmpbuf4len = 512; + tmpbuf4 = alloca (tmpbuf4len); + } + else + { + tmpbuf4len = tmpbuf6len; + tmpbuf4 = tmpbuf6; + } + + /* Next collect IPv4 information. */ + while (1) + { + rc4 = 0; + status[1] = DL_CALL_FCT (fct, (key, AF_INET, &th[1], tmpbuf4, + tmpbuf4len, &rc4, &herrno, + ttl == INT32_MAX ? &ttl : NULL, + canon == NULL ? &canon : NULL)); + if (rc4 != ERANGE || herrno != NETDB_INTERNAL) + break; + tmpbuf4 = extend_alloca (tmpbuf4, tmpbuf4len, 2 * tmpbuf4len); + } + + if (rc4 != 0 && herrno == NETDB_INTERNAL) + goto out; + + if (status[0] != NSS_STATUS_SUCCESS + && status[1] != NSS_STATUS_SUCCESS) + goto next_nip; + + /* We found the data. Count the addresses and the size. */ + for (int j = 0; j < 2; ++j) + if (status[j] == NSS_STATUS_SUCCESS) + for (int i = 0; th[j].h_addr_list[i] != NULL; ++i) + { + ++naddrs; + addrslen += th[j].h_length; + } + + if (canon == NULL) + { + /* Determine the canonical name. */ + nss_getcanonname_r cfct; + cfct = __nss_lookup_function (nip, "getcanonname_r"); + if (cfct != NULL) + { + const size_t max_fqdn_len = 256; + char *buf = alloca (max_fqdn_len); + char *s; + int rc; + + if (DL_CALL_FCT (cfct, (key, buf, max_fqdn_len, &s, + &rc, &herrno)) + == NSS_STATUS_SUCCESS) + canon = s; + else + /* Set to name now to avoid using gethostbyaddr. */ + canon = key; + } + else + { + struct hostent *hstent = NULL; + int herrno; + struct hostent hstent_mem; + void *addr; + size_t addrlen; + int addrfamily; + + if (status[1] == NSS_STATUS_SUCCESS) + { + addr = th[1].h_addr_list[0]; + addrlen = sizeof (struct in_addr); + addrfamily = AF_INET; + } + else + { + addr = th[0].h_addr_list[0]; + addrlen = sizeof (struct in6_addr); + addrfamily = AF_INET6; + } + + size_t tmpbuflen = 512; + char *tmpbuf = alloca (tmpbuflen); + int rc; + while (1) + { + rc = __gethostbyaddr2_r (addr, addrlen, addrfamily, + &hstent_mem, tmpbuf, tmpbuflen, + &hstent, &herrno, NULL); + if (rc != ERANGE || herrno != NETDB_INTERNAL) + break; + tmpbuf = extend_alloca (tmpbuf, tmpbuflen, + tmpbuflen * 2); + } + + if (rc == 0) + { + if (hstent != NULL) + canon = hstent->h_name; + else + canon = key; + } + } + } + + canonlen = canon == NULL ? 0 : (strlen (canon) + 1); + + total = sizeof (*dataset) + naddrs + addrslen + canonlen; + + + /* Now we can allocate the data structure. If the TTL of the + entry is reported as zero do not cache the entry at all. */ + if (ttl != 0 && he == NULL) + dataset = (struct dataset *) mempool_alloc (db, total + + req->key_len, 1); + + if (dataset == NULL) + { + /* We cannot permanently add the result in the moment. But + we can provide the result as is. Store the data in some + temporary memory. */ + dataset = (struct dataset *) alloca (total + req->key_len); + + /* We cannot add this record to the permanent database. */ + alloca_used = true; + } + + /* Fill in the address and address families. */ + char *addrs = dataset->strdata; + uint8_t *family = (uint8_t *) (addrs + addrslen); + + for (int j = 0; j < 2; ++j) + if (status[j] == NSS_STATUS_SUCCESS) + for (int i = 0; th[j].h_addr_list[i] != NULL; ++i) + { + addrs = mempcpy (addrs, th[j].h_addr_list[i], + th[j].h_length); + *family++ = th[j].h_addrtype; + } + + cp = family; + } + + timeout = datahead_init_pos (&dataset->head, total + req->key_len, + total - offsetof (struct dataset, resp), + he == NULL ? 0 : dh->nreloads + 1, + ttl == INT32_MAX ? db->postimeout : ttl); + + /* Fill in the rest of the dataset. */ + dataset->resp.version = NSCD_VERSION; + dataset->resp.found = 1; + dataset->resp.naddrs = naddrs; + dataset->resp.addrslen = addrslen; + dataset->resp.canonlen = canonlen; + dataset->resp.error = NETDB_SUCCESS; + + if (canon != NULL) + cp = mempcpy (cp, canon, canonlen); + + key_copy = memcpy (cp, key, req->key_len); + + assert (cp == (char *) dataset + total); + + /* Now we can determine whether on refill we have to create a + new record or not. */ + if (he != NULL) + { + assert (fd == -1); + + if (total + req->key_len == dh->allocsize + && total - offsetof (struct dataset, resp) == dh->recsize + && memcmp (&dataset->resp, dh->data, + dh->allocsize - offsetof (struct dataset, + resp)) == 0) + { + /* The data has not changed. We will just bump the + timeout value. Note that the new record has been + allocated on the stack and need not be freed. */ + dh->timeout = dataset->head.timeout; + dh->ttl = dataset->head.ttl; + ++dh->nreloads; + } + else + { + /* We have to create a new record. Just allocate + appropriate memory and copy it. */ + struct dataset *newp + = (struct dataset *) mempool_alloc (db, total + req->key_len, + 1); + if (__glibc_likely (newp != NULL)) + { + /* Adjust pointer into the memory block. */ + key_copy = (char *) newp + (key_copy - (char *) dataset); + + dataset = memcpy (newp, dataset, total + req->key_len); + alloca_used = false; + } + + /* Mark the old record as obsolete. */ + dh->usable = false; + } + } + else + { + /* We write the dataset before inserting it to the database + since while inserting this thread might block and so + would unnecessarily let the receiver wait. */ + assert (fd != -1); + +#ifdef HAVE_SENDFILE + if (__builtin_expect (db->mmap_used, 1) && !alloca_used) + { + assert (db->wr_fd != -1); + assert ((char *) &dataset->resp > (char *) db->data); + assert ((char *) dataset - (char *) db->head + total + <= (sizeof (struct database_pers_head) + + db->head->module * sizeof (ref_t) + + db->head->data_size)); +# ifndef __ASSUME_SENDFILE + ssize_t written; + written = +# endif + sendfileall (fd, db->wr_fd, (char *) &dataset->resp + - (char *) db->head, dataset->head.recsize); +# ifndef __ASSUME_SENDFILE + if (written == -1 && errno == ENOSYS) + goto use_write; +# endif + } + else +# ifndef __ASSUME_SENDFILE + use_write: +# endif +#endif + writeall (fd, &dataset->resp, dataset->head.recsize); + } + + goto out; + +next_nip: + if (nss_next_action (nip, status[1]) == NSS_ACTION_RETURN) + break; + + if (nip->next == NULL) + no_more = -1; + else + nip = nip->next; + } + + /* No result found. Create a negative result record. */ + if (he != NULL && rc4 == EAGAIN) + { + /* If we have an old record available but cannot find one now + because the service is not available we keep the old record + and make sure it does not get removed. */ + if (reload_count != UINT_MAX && dh->nreloads == reload_count) + /* Do not reset the value if we never not reload the record. */ + dh->nreloads = reload_count - 1; + + /* Reload with the same time-to-live value. */ + timeout = dh->timeout = time (NULL) + dh->ttl; + } + else + { + /* We have no data. This means we send the standard reply for + this case. */ + total = sizeof (notfound); + + if (fd != -1) + TEMP_FAILURE_RETRY (send (fd, ¬found, total, MSG_NOSIGNAL)); + + /* If we have a transient error or cannot permanently store the + result, so be it. */ + if (rc4 == EAGAIN || __builtin_expect (db->negtimeout == 0, 0)) + { + /* Mark the old entry as obsolete. */ + if (dh != NULL) + dh->usable = false; + dataset = NULL; + } + else if ((dataset = mempool_alloc (db, (sizeof (struct dataset) + + req->key_len), 1)) != NULL) + { + timeout = datahead_init_neg (&dataset->head, + sizeof (struct dataset) + req->key_len, + total, db->negtimeout); + + /* This is the reply. */ + memcpy (&dataset->resp, ¬found, total); + + /* Copy the key data. */ + key_copy = memcpy (dataset->strdata, key, req->key_len); + } + } + + out: + _res.options |= old_res_options & DEPRECATED_RES_USE_INET6; + + if (dataset != NULL && !alloca_used) + { + /* If necessary, we also propagate the data to disk. */ + if (db->persistent) + { + // XXX async OK? + uintptr_t pval = (uintptr_t) dataset & ~pagesize_m1; + msync ((void *) pval, + ((uintptr_t) dataset & pagesize_m1) + total + req->key_len, + MS_ASYNC); + } + + (void) cache_add (req->type, key_copy, req->key_len, &dataset->head, + true, db, uid, he == NULL); + + pthread_rwlock_unlock (&db->lock); + + /* Mark the old entry as obsolete. */ + if (dh != NULL) + dh->usable = false; + } + + return timeout; +} + + +void +addhstai (struct database_dyn *db, int fd, request_header *req, void *key, + uid_t uid) +{ + addhstaiX (db, fd, req, key, uid, NULL, NULL); +} + + +time_t +readdhstai (struct database_dyn *db, struct hashentry *he, struct datahead *dh) +{ + request_header req = + { + .type = GETAI, + .key_len = he->len + }; + + return addhstaiX (db, -1, &req, db->data + he->key, he->owner, he, dh); +} |