diff options
Diffstat (limited to 'REORG.TODO/nscd/netgroupcache.c')
-rw-r--r-- | REORG.TODO/nscd/netgroupcache.c | 699 |
1 files changed, 699 insertions, 0 deletions
diff --git a/REORG.TODO/nscd/netgroupcache.c b/REORG.TODO/nscd/netgroupcache.c new file mode 100644 index 0000000000..cd0c3ea19b --- /dev/null +++ b/REORG.TODO/nscd/netgroupcache.c @@ -0,0 +1,699 @@ +/* Cache handling for netgroup lookup. + Copyright (C) 2011-2017 Free Software Foundation, Inc. + This file is part of the GNU C Library. + Contributed by Ulrich Drepper <drepper@gmail.com>, 2011. + + 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 <alloca.h> +#include <assert.h> +#include <errno.h> +#include <libintl.h> +#include <stdbool.h> +#include <stdlib.h> +#include <unistd.h> +#include <sys/mman.h> + +#include "../inet/netgroup.h" +#include "nscd.h" +#include "dbg_log.h" + +#include <kernel-features.h> + + +/* This is the standard reply in case the service is disabled. */ +static const netgroup_response_header disabled = +{ + .version = NSCD_VERSION, + .found = -1, + .nresults = 0, + .result_len = 0 +}; + +/* This is the struct describing how to write this record. */ +const struct iovec netgroup_iov_disabled = +{ + .iov_base = (void *) &disabled, + .iov_len = sizeof (disabled) +}; + + +/* This is the standard reply in case we haven't found the dataset. */ +static const netgroup_response_header notfound = +{ + .version = NSCD_VERSION, + .found = 0, + .nresults = 0, + .result_len = 0 +}; + + +struct dataset +{ + struct datahead head; + netgroup_response_header resp; + char strdata[0]; +}; + +/* Sends a notfound message and prepares a notfound dataset to write to the + cache. Returns true if there was enough memory to allocate the dataset and + returns the dataset in DATASETP, total bytes to write in TOTALP and the + timeout in TIMEOUTP. KEY_COPY is set to point to the copy of the key in the + dataset. */ +static bool +do_notfound (struct database_dyn *db, int fd, request_header *req, + const char *key, struct dataset **datasetp, ssize_t *totalp, + time_t *timeoutp, char **key_copy) +{ + struct dataset *dataset; + ssize_t total; + time_t timeout; + bool cacheable = false; + + total = sizeof (notfound); + timeout = time (NULL) + db->negtimeout; + + if (fd != -1) + TEMP_FAILURE_RETRY (send (fd, ¬found, total, MSG_NOSIGNAL)); + + dataset = mempool_alloc (db, sizeof (struct dataset) + req->key_len, 1); + /* If we cannot permanently store the result, so be it. */ + if (dataset != 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. */ + memcpy (dataset->strdata, key, req->key_len); + *key_copy = dataset->strdata; + + cacheable = true; + } + *timeoutp = timeout; + *totalp = total; + *datasetp = dataset; + return cacheable; +} + +static time_t +addgetnetgrentX (struct database_dyn *db, int fd, request_header *req, + const char *key, uid_t uid, struct hashentry *he, + struct datahead *dh, struct dataset **resultp) +{ + if (__glibc_unlikely (debug_level > 0)) + { + if (he == NULL) + dbg_log (_("Haven't found \"%s\" in netgroup cache!"), key); + else + dbg_log (_("Reloading \"%s\" in netgroup cache!"), key); + } + + static service_user *netgroup_database; + time_t timeout; + struct dataset *dataset; + bool cacheable = false; + ssize_t total; + bool found = false; + + char *key_copy = NULL; + struct __netgrent data; + size_t buflen = MAX (1024, sizeof (*dataset) + req->key_len); + size_t buffilled = sizeof (*dataset); + char *buffer = NULL; + size_t nentries = 0; + size_t group_len = strlen (key) + 1; + struct name_list *first_needed + = alloca (sizeof (struct name_list) + group_len); + + if (netgroup_database == NULL + && __nss_database_lookup ("netgroup", NULL, NULL, &netgroup_database)) + { + /* No such service. */ + cacheable = do_notfound (db, fd, req, key, &dataset, &total, &timeout, + &key_copy); + goto writeout; + } + + memset (&data, '\0', sizeof (data)); + buffer = xmalloc (buflen); + first_needed->next = first_needed; + memcpy (first_needed->name, key, group_len); + data.needed_groups = first_needed; + + while (data.needed_groups != NULL) + { + /* Add the next group to the list of those which are known. */ + struct name_list *this_group = data.needed_groups->next; + if (this_group == data.needed_groups) + data.needed_groups = NULL; + else + data.needed_groups->next = this_group->next; + this_group->next = data.known_groups; + data.known_groups = this_group; + + union + { + enum nss_status (*f) (const char *, struct __netgrent *); + void *ptr; + } setfct; + + service_user *nip = netgroup_database; + int no_more = __nss_lookup (&nip, "setnetgrent", NULL, &setfct.ptr); + while (!no_more) + { + enum nss_status status + = DL_CALL_FCT (*setfct.f, (data.known_groups->name, &data)); + + if (status == NSS_STATUS_SUCCESS) + { + found = true; + union + { + enum nss_status (*f) (struct __netgrent *, char *, size_t, + int *); + void *ptr; + } getfct; + getfct.ptr = __nss_lookup_function (nip, "getnetgrent_r"); + if (getfct.f != NULL) + while (1) + { + int e; + status = getfct.f (&data, buffer + buffilled, + buflen - buffilled - req->key_len, &e); + if (status == NSS_STATUS_SUCCESS) + { + if (data.type == triple_val) + { + const char *nhost = data.val.triple.host; + const char *nuser = data.val.triple.user; + const char *ndomain = data.val.triple.domain; + + size_t hostlen = strlen (nhost ?: "") + 1; + size_t userlen = strlen (nuser ?: "") + 1; + size_t domainlen = strlen (ndomain ?: "") + 1; + + if (nhost == NULL || nuser == NULL || ndomain == NULL + || nhost > nuser || nuser > ndomain) + { + const char *last = nhost; + if (last == NULL + || (nuser != NULL && nuser > last)) + last = nuser; + if (last == NULL + || (ndomain != NULL && ndomain > last)) + last = ndomain; + + size_t bufused + = (last == NULL + ? buffilled + : last + strlen (last) + 1 - buffer); + + /* We have to make temporary copies. */ + size_t needed = hostlen + userlen + domainlen; + + if (buflen - req->key_len - bufused < needed) + { + buflen += MAX (buflen, 2 * needed); + /* Save offset in the old buffer. We don't + bother with the NULL check here since + we'll do that later anyway. */ + size_t nhostdiff = nhost - buffer; + size_t nuserdiff = nuser - buffer; + size_t ndomaindiff = ndomain - buffer; + + char *newbuf = xrealloc (buffer, buflen); + /* Fix up the triplet pointers into the new + buffer. */ + nhost = (nhost ? newbuf + nhostdiff + : NULL); + nuser = (nuser ? newbuf + nuserdiff + : NULL); + ndomain = (ndomain ? newbuf + ndomaindiff + : NULL); + buffer = newbuf; + } + + nhost = memcpy (buffer + bufused, + nhost ?: "", hostlen); + nuser = memcpy ((char *) nhost + hostlen, + nuser ?: "", userlen); + ndomain = memcpy ((char *) nuser + userlen, + ndomain ?: "", domainlen); + } + + char *wp = buffer + buffilled; + wp = memmove (wp, nhost ?: "", hostlen); + wp += hostlen; + wp = memmove (wp, nuser ?: "", userlen); + wp += userlen; + wp = memmove (wp, ndomain ?: "", domainlen); + wp += domainlen; + buffilled = wp - buffer; + ++nentries; + } + else + { + /* Check that the group has not been + requested before. */ + struct name_list *runp = data.needed_groups; + if (runp != NULL) + while (1) + { + if (strcmp (runp->name, data.val.group) == 0) + break; + + runp = runp->next; + if (runp == data.needed_groups) + { + runp = NULL; + break; + } + } + + if (runp == NULL) + { + runp = data.known_groups; + while (runp != NULL) + if (strcmp (runp->name, data.val.group) == 0) + break; + else + runp = runp->next; + } + + if (runp == NULL) + { + /* A new group is requested. */ + size_t namelen = strlen (data.val.group) + 1; + struct name_list *newg = alloca (sizeof (*newg) + + namelen); + memcpy (newg->name, data.val.group, namelen); + if (data.needed_groups == NULL) + data.needed_groups = newg->next = newg; + else + { + newg->next = data.needed_groups->next; + data.needed_groups->next = newg; + data.needed_groups = newg; + } + } + } + } + else if (status == NSS_STATUS_TRYAGAIN && e == ERANGE) + { + buflen *= 2; + buffer = xrealloc (buffer, buflen); + } + else if (status == NSS_STATUS_RETURN + || status == NSS_STATUS_NOTFOUND + || status == NSS_STATUS_UNAVAIL) + /* This was either the last one for this group or the + group was empty or the NSS module had an internal + failure. Look at next group if available. */ + break; + } + + enum nss_status (*endfct) (struct __netgrent *); + endfct = __nss_lookup_function (nip, "endnetgrent"); + if (endfct != NULL) + (void) DL_CALL_FCT (*endfct, (&data)); + + break; + } + + no_more = __nss_next2 (&nip, "setnetgrent", NULL, &setfct.ptr, + status, 0); + } + } + + /* No results. Return a failure and write out a notfound record in the + cache. */ + if (!found) + { + cacheable = do_notfound (db, fd, req, key, &dataset, &total, &timeout, + &key_copy); + goto writeout; + } + + total = buffilled; + + /* Fill in the dataset. */ + dataset = (struct dataset *) buffer; + timeout = datahead_init_pos (&dataset->head, total + req->key_len, + total - offsetof (struct dataset, resp), + he == NULL ? 0 : dh->nreloads + 1, + db->postimeout); + + dataset->resp.version = NSCD_VERSION; + dataset->resp.found = 1; + dataset->resp.nresults = nentries; + dataset->resp.result_len = buffilled - sizeof (*dataset); + + assert (buflen - buffilled >= req->key_len); + key_copy = memcpy (buffer + buffilled, key, req->key_len); + buffilled += req->key_len; + + /* Now we can determine whether on refill we have to create a new + record or not. */ + if (he != NULL) + { + assert (fd == -1); + + if (dataset->head.allocsize == dh->allocsize + && dataset->head.recsize == 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; + dataset = (struct dataset *) dh; + + goto out; + } + } + + { + 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 - buffer); + + dataset = memcpy (newp, dataset, total + req->key_len); + cacheable = true; + + if (he != NULL) + /* Mark the old record as obsolete. */ + dh->usable = false; + } + } + + if (he == NULL && fd != -1) + { + /* 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. */ + writeout: +#ifdef HAVE_SENDFILE + if (__builtin_expect (db->mmap_used, 1) && cacheable) + { + 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 = +# 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 +#endif + { +#if defined HAVE_SENDFILE && !defined __ASSUME_SENDFILE + use_write: +#endif + writeall (fd, &dataset->resp, dataset->head.recsize); + } + } + + if (cacheable) + { + /* 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; + } + + out: + free (buffer); + + *resultp = dataset; + + return timeout; +} + + +static time_t +addinnetgrX (struct database_dyn *db, int fd, request_header *req, + char *key, uid_t uid, struct hashentry *he, + struct datahead *dh) +{ + const char *group = key; + key = (char *) rawmemchr (key, '\0') + 1; + size_t group_len = key - group - 1; + const char *host = *key++ ? key : NULL; + if (host != NULL) + key = (char *) rawmemchr (key, '\0') + 1; + const char *user = *key++ ? key : NULL; + if (user != NULL) + key = (char *) rawmemchr (key, '\0') + 1; + const char *domain = *key++ ? key : NULL; + + if (__glibc_unlikely (debug_level > 0)) + { + if (he == NULL) + dbg_log (_("Haven't found \"%s (%s,%s,%s)\" in netgroup cache!"), + group, host ?: "", user ?: "", domain ?: ""); + else + dbg_log (_("Reloading \"%s (%s,%s,%s)\" in netgroup cache!"), + group, host ?: "", user ?: "", domain ?: ""); + } + + struct dataset *result = (struct dataset *) cache_search (GETNETGRENT, + group, group_len, + db, uid); + time_t timeout; + if (result != NULL) + timeout = result->head.timeout; + else + { + request_header req_get = + { + .type = GETNETGRENT, + .key_len = group_len + }; + timeout = addgetnetgrentX (db, -1, &req_get, group, uid, NULL, NULL, + &result); + } + + struct indataset + { + struct datahead head; + innetgroup_response_header resp; + } *dataset + = (struct indataset *) mempool_alloc (db, + sizeof (*dataset) + req->key_len, + 1); + struct indataset dataset_mem; + bool cacheable = true; + if (__glibc_unlikely (dataset == NULL)) + { + cacheable = false; + dataset = &dataset_mem; + } + + datahead_init_pos (&dataset->head, sizeof (*dataset) + req->key_len, + sizeof (innetgroup_response_header), + he == NULL ? 0 : dh->nreloads + 1, result->head.ttl); + /* Set the notfound status and timeout based on the result from + getnetgrent. */ + dataset->head.notfound = result->head.notfound; + dataset->head.timeout = timeout; + + dataset->resp.version = NSCD_VERSION; + dataset->resp.found = result->resp.found; + /* Until we find a matching entry the result is 0. */ + dataset->resp.result = 0; + + char *key_copy = memcpy ((char *) (dataset + 1), group, req->key_len); + + if (dataset->resp.found) + { + const char *triplets = (const char *) (&result->resp + 1); + + for (nscd_ssize_t i = result->resp.nresults; i > 0; --i) + { + bool success = true; + + /* For the host, user and domain in each triplet, we assume success + if the value is blank because that is how the wildcard entry to + match anything is stored in the netgroup cache. */ + if (host != NULL && *triplets != '\0') + success = strcmp (host, triplets) == 0; + triplets = (const char *) rawmemchr (triplets, '\0') + 1; + + if (success && user != NULL && *triplets != '\0') + success = strcmp (user, triplets) == 0; + triplets = (const char *) rawmemchr (triplets, '\0') + 1; + + if (success && (domain == NULL || *triplets == '\0' + || strcmp (domain, triplets) == 0)) + { + dataset->resp.result = 1; + break; + } + triplets = (const char *) rawmemchr (triplets, '\0') + 1; + } + } + + if (he != NULL && dh->data[0].innetgroupdata.result == dataset->resp.result) + { + /* 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 = timeout; + dh->ttl = dataset->head.ttl; + ++dh->nreloads; + return timeout; + } + + if (he == NULL) + { + /* 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) && cacheable) + { + assert (db->wr_fd != -1); + assert ((char *) &dataset->resp > (char *) db->data); + assert ((char *) dataset - (char *) db->head + sizeof (*dataset) + <= (sizeof (struct database_pers_head) + + db->head->module * sizeof (ref_t) + + db->head->data_size)); +# ifndef __ASSUME_SENDFILE + ssize_t written = +# endif + sendfileall (fd, db->wr_fd, + (char *) &dataset->resp - (char *) db->head, + sizeof (innetgroup_response_header)); +# ifndef __ASSUME_SENDFILE + if (written == -1 && errno == ENOSYS) + goto use_write; +# endif + } + else +#endif + { +#if defined HAVE_SENDFILE && !defined __ASSUME_SENDFILE + use_write: +#endif + writeall (fd, &dataset->resp, sizeof (innetgroup_response_header)); + } + } + + if (cacheable) + { + /* 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) + sizeof (*dataset) + + 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 +addgetnetgrent (struct database_dyn *db, int fd, request_header *req, + void *key, uid_t uid) +{ + struct dataset *ignore; + + addgetnetgrentX (db, fd, req, key, uid, NULL, NULL, &ignore); +} + + +time_t +readdgetnetgrent (struct database_dyn *db, struct hashentry *he, + struct datahead *dh) +{ + request_header req = + { + .type = GETNETGRENT, + .key_len = he->len + }; + struct dataset *ignore; + + return addgetnetgrentX (db, -1, &req, db->data + he->key, he->owner, he, dh, + &ignore); +} + + +void +addinnetgr (struct database_dyn *db, int fd, request_header *req, + void *key, uid_t uid) +{ + addinnetgrX (db, fd, req, key, uid, NULL, NULL); +} + + +time_t +readdinnetgr (struct database_dyn *db, struct hashentry *he, + struct datahead *dh) +{ + request_header req = + { + .type = INNETGR, + .key_len = he->len + }; + + return addinnetgrX (db, -1, &req, db->data + he->key, he->owner, he, dh); +} |