aboutsummaryrefslogtreecommitdiff
path: root/REORG.TODO/nscd
diff options
context:
space:
mode:
Diffstat (limited to 'REORG.TODO/nscd')
-rw-r--r--REORG.TODO/nscd/Depend1
-rw-r--r--REORG.TODO/nscd/Makefile103
-rw-r--r--REORG.TODO/nscd/aicache.c585
-rw-r--r--REORG.TODO/nscd/cache.c540
-rw-r--r--REORG.TODO/nscd/connections.c2558
-rw-r--r--REORG.TODO/nscd/dbg_log.c85
-rw-r--r--REORG.TODO/nscd/dbg_log.h30
-rw-r--r--REORG.TODO/nscd/gai.c48
-rw-r--r--REORG.TODO/nscd/getgrgid_r.c35
-rw-r--r--REORG.TODO/nscd/getgrnam_r.c34
-rw-r--r--REORG.TODO/nscd/gethstbyad_r.c46
-rw-r--r--REORG.TODO/nscd/gethstbynm3_r.c55
-rw-r--r--REORG.TODO/nscd/getpwnam_r.c31
-rw-r--r--REORG.TODO/nscd/getpwuid_r.c31
-rw-r--r--REORG.TODO/nscd/getsrvbynm_r.c30
-rw-r--r--REORG.TODO/nscd/getsrvbypt_r.c30
-rw-r--r--REORG.TODO/nscd/grpcache.c572
-rw-r--r--REORG.TODO/nscd/hstcache.c619
-rw-r--r--REORG.TODO/nscd/initgrcache.c438
-rw-r--r--REORG.TODO/nscd/mem.c589
-rw-r--r--REORG.TODO/nscd/netgroupcache.c699
-rw-r--r--REORG.TODO/nscd/nscd-client.h452
-rw-r--r--REORG.TODO/nscd/nscd.c700
-rw-r--r--REORG.TODO/nscd/nscd.conf88
-rw-r--r--REORG.TODO/nscd/nscd.h377
-rw-r--r--REORG.TODO/nscd/nscd.init116
-rw-r--r--REORG.TODO/nscd/nscd.service19
-rw-r--r--REORG.TODO/nscd/nscd.tmpfiles4
-rw-r--r--REORG.TODO/nscd/nscd_conf.c315
-rw-r--r--REORG.TODO/nscd/nscd_getai.c216
-rw-r--r--REORG.TODO/nscd/nscd_getgr_r.c330
-rw-r--r--REORG.TODO/nscd/nscd_gethst_r.c459
-rw-r--r--REORG.TODO/nscd/nscd_getpw_r.c241
-rw-r--r--REORG.TODO/nscd/nscd_getserv_r.c388
-rw-r--r--REORG.TODO/nscd/nscd_helper.c564
-rw-r--r--REORG.TODO/nscd/nscd_initgroups.c180
-rw-r--r--REORG.TODO/nscd/nscd_netgroup.c289
-rw-r--r--REORG.TODO/nscd/nscd_proto.h79
-rw-r--r--REORG.TODO/nscd/nscd_setup_thread.c27
-rw-r--r--REORG.TODO/nscd/nscd_stat.c318
-rw-r--r--REORG.TODO/nscd/pwdcache.c552
-rw-r--r--REORG.TODO/nscd/res_hconf.c13
-rw-r--r--REORG.TODO/nscd/selinux.c453
-rw-r--r--REORG.TODO/nscd/selinux.h61
-rw-r--r--REORG.TODO/nscd/servicescache.c474
45 files changed, 13874 insertions, 0 deletions
diff --git a/REORG.TODO/nscd/Depend b/REORG.TODO/nscd/Depend
new file mode 100644
index 0000000000..6c1aa44e6e
--- /dev/null
+++ b/REORG.TODO/nscd/Depend
@@ -0,0 +1 @@
+nptl
diff --git a/REORG.TODO/nscd/Makefile b/REORG.TODO/nscd/Makefile
new file mode 100644
index 0000000000..4126996887
--- /dev/null
+++ b/REORG.TODO/nscd/Makefile
@@ -0,0 +1,103 @@
+# Copyright (C) 1998-2017 Free Software Foundation, Inc.
+# This file is part of the GNU C Library.
+
+# The GNU C Library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+
+# The GNU C Library 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
+# Lesser General Public License for more details.
+
+# You should have received a copy of the GNU Lesser General Public
+# License along with the GNU C Library; if not, see
+# <http://www.gnu.org/licenses/>.
+
+#
+# Sub-makefile for nscd portion of the library.
+#
+subdir := nscd
+
+include ../Makeconfig
+
+ifneq ($(use-nscd),no)
+routines := nscd_getpw_r nscd_getgr_r nscd_gethst_r nscd_getai \
+ nscd_initgroups nscd_getserv_r nscd_netgroup
+aux := nscd_helper
+endif
+
+# To find xmalloc.c
+vpath %.c ../locale/programs
+
+nscd-modules := nscd connections pwdcache getpwnam_r getpwuid_r grpcache \
+ getgrnam_r getgrgid_r hstcache gethstbyad_r gethstbynm3_r \
+ getsrvbynm_r getsrvbypt_r servicescache \
+ dbg_log nscd_conf nscd_stat cache mem nscd_setup_thread \
+ xmalloc xstrdup aicache initgrcache gai res_hconf \
+ netgroupcache
+
+ifeq ($(build-nscd)$(have-thread-library),yesyes)
+
+others += nscd
+others-pie += nscd
+install-sbin := nscd
+
+extra-objs = $(nscd-modules:=.o)
+
+endif
+
+all-nscd-modules := $(nscd-modules) selinux
+ifeq (yes,$(have-selinux))
+ifeq (yes,$(have-libaudit))
+libaudit = -laudit
+ifeq (yes,$(have-libcap))
+libcap = -lcap
+endif
+endif
+
+nscd-modules += selinux
+selinux-LIBS := -lselinux $(libaudit) $(libcap)
+
+# The configure.ac check for libselinux and its headers did not use
+# $SYSINCLUDES. The directory specified by --with-headers usually
+# contains only the basic kernel interface headers, not something like
+# libselinux. So the simplest thing is to presume that the standard
+# system headers will be ok for this file.
+$(objpfx)nscd_stat.o: sysincludes = # nothing
+$(objpfx)selinux.o: sysincludes = # nothing
+endif
+
+LDLIBS-nscd = $(selinux-LIBS)
+
+include ../Rules
+
+CFLAGS-nscd_getpw_r.c = -fexceptions
+CFLAGS-nscd_getgr_r.c = -fexceptions
+CFLAGS-nscd_gethst_r.c = -fexceptions
+CFLAGS-nscd_getai.c = -fexceptions
+CFLAGS-nscd_initgroups.c = -fexceptions
+
+CPPFLAGS-nscd += -D_FORTIFY_SOURCE=2
+
+ifeq (yesyes,$(have-fpie)$(build-shared))
+CFLAGS-nscd += $(pie-ccflag)
+endif
+
+ifeq (yesyes,$(have-fpie)$(build-shared))
+LDFLAGS-nscd = -Wl,-z,now
+endif
+
+# Set libof-nscd.
+cpp-srcs-left := $(nscd-modules)
+lib := nscd
+include $(patsubst %,$(..)libof-iterator.mk,$(cpp-srcs-left))
+
+$(objpfx)nscd: $(nscd-modules:%=$(objpfx)%.o)
+
+ifeq ($(build-shared),yes)
+$(objpfx)nscd: $(shared-thread-library) $(common-objpfx)nis/libnsl.so
+else
+$(objpfx)nscd: $(static-thread-library) $(common-objpfx)nis/libnsl.a
+endif
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, &notfound, 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, &notfound, 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);
+}
diff --git a/REORG.TODO/nscd/cache.c b/REORG.TODO/nscd/cache.c
new file mode 100644
index 0000000000..b9dbc7a0bd
--- /dev/null
+++ b/REORG.TODO/nscd/cache.c
@@ -0,0 +1,540 @@
+/* Copyright (c) 1998-2017 Free Software Foundation, Inc.
+ This file is part of the GNU C Library.
+ Contributed by Ulrich Drepper <drepper@cygnus.com>, 1998.
+
+ 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 <atomic.h>
+#include <errno.h>
+#include <error.h>
+#include <inttypes.h>
+#include <limits.h>
+#include <stdlib.h>
+#include <string.h>
+#include <libintl.h>
+#include <arpa/inet.h>
+#include <rpcsvc/nis.h>
+#include <sys/mman.h>
+#include <sys/param.h>
+#include <sys/stat.h>
+#include <sys/uio.h>
+
+#include "nscd.h"
+#include "dbg_log.h"
+
+
+/* Wrapper functions with error checking for standard functions. */
+extern void *xcalloc (size_t n, size_t s);
+
+
+/* Number of times a value is reloaded without being used. UINT_MAX
+ means unlimited. */
+unsigned int reload_count = DEFAULT_RELOAD_LIMIT;
+
+
+static time_t (*const readdfcts[LASTREQ]) (struct database_dyn *,
+ struct hashentry *,
+ struct datahead *) =
+{
+ [GETPWBYNAME] = readdpwbyname,
+ [GETPWBYUID] = readdpwbyuid,
+ [GETGRBYNAME] = readdgrbyname,
+ [GETGRBYGID] = readdgrbygid,
+ [GETHOSTBYNAME] = readdhstbyname,
+ [GETHOSTBYNAMEv6] = readdhstbynamev6,
+ [GETHOSTBYADDR] = readdhstbyaddr,
+ [GETHOSTBYADDRv6] = readdhstbyaddrv6,
+ [GETAI] = readdhstai,
+ [INITGROUPS] = readdinitgroups,
+ [GETSERVBYNAME] = readdservbyname,
+ [GETSERVBYPORT] = readdservbyport,
+ [GETNETGRENT] = readdgetnetgrent,
+ [INNETGR] = readdinnetgr
+};
+
+
+/* Search the cache for a matching entry and return it when found. If
+ this fails search the negative cache and return (void *) -1 if this
+ search was successful. Otherwise return NULL.
+
+ This function must be called with the read-lock held. */
+struct datahead *
+cache_search (request_type type, const void *key, size_t len,
+ struct database_dyn *table, uid_t owner)
+{
+ unsigned long int hash = __nis_hash (key, len) % table->head->module;
+
+ unsigned long int nsearched = 0;
+ struct datahead *result = NULL;
+
+ ref_t work = table->head->array[hash];
+ while (work != ENDREF)
+ {
+ ++nsearched;
+
+ struct hashentry *here = (struct hashentry *) (table->data + work);
+
+ if (type == here->type && len == here->len
+ && memcmp (key, table->data + here->key, len) == 0
+ && here->owner == owner)
+ {
+ /* We found the entry. Increment the appropriate counter. */
+ struct datahead *dh
+ = (struct datahead *) (table->data + here->packet);
+
+ /* See whether we must ignore the entry. */
+ if (dh->usable)
+ {
+ /* We do not synchronize the memory here. The statistics
+ data is not crucial, we synchronize only once in a while
+ in the cleanup threads. */
+ if (dh->notfound)
+ ++table->head->neghit;
+ else
+ {
+ ++table->head->poshit;
+
+ if (dh->nreloads != 0)
+ dh->nreloads = 0;
+ }
+
+ result = dh;
+ break;
+ }
+ }
+
+ work = here->next;
+ }
+
+ if (nsearched > table->head->maxnsearched)
+ table->head->maxnsearched = nsearched;
+
+ return result;
+}
+
+/* Add a new entry to the cache. The return value is zero if the function
+ call was successful.
+
+ This function must be called with the read-lock held.
+
+ We modify the table but we nevertheless only acquire a read-lock.
+ This is ok since we use operations which would be safe even without
+ locking, given that the `prune_cache' function never runs. Using
+ the readlock reduces the chance of conflicts. */
+int
+cache_add (int type, const void *key, size_t len, struct datahead *packet,
+ bool first, struct database_dyn *table,
+ uid_t owner, bool prune_wakeup)
+{
+ if (__glibc_unlikely (debug_level >= 2))
+ {
+ const char *str;
+ char buf[INET6_ADDRSTRLEN + 1];
+ if (type == GETHOSTBYADDR || type == GETHOSTBYADDRv6)
+ str = inet_ntop (type == GETHOSTBYADDR ? AF_INET : AF_INET6,
+ key, buf, sizeof (buf));
+ else
+ str = key;
+
+ dbg_log (_("add new entry \"%s\" of type %s for %s to cache%s"),
+ str, serv2str[type], dbnames[table - dbs],
+ first ? _(" (first)") : "");
+ }
+
+ unsigned long int hash = __nis_hash (key, len) % table->head->module;
+ struct hashentry *newp;
+
+ newp = mempool_alloc (table, sizeof (struct hashentry), 0);
+ /* If we cannot allocate memory, just do not do anything. */
+ if (newp == NULL)
+ {
+ /* If necessary mark the entry as unusable so that lookups will
+ not use it. */
+ if (first)
+ packet->usable = false;
+
+ return -1;
+ }
+
+ newp->type = type;
+ newp->first = first;
+ newp->len = len;
+ newp->key = (char *) key - table->data;
+ assert (newp->key + newp->len <= table->head->first_free);
+ newp->owner = owner;
+ newp->packet = (char *) packet - table->data;
+ assert ((newp->packet & BLOCK_ALIGN_M1) == 0);
+
+ /* Put the new entry in the first position. */
+ /* TODO Review concurrency. Use atomic_exchange_release. */
+ newp->next = atomic_load_relaxed (&table->head->array[hash]);
+ while (!atomic_compare_exchange_weak_release (&table->head->array[hash],
+ (ref_t *) &newp->next,
+ (ref_t) ((char *) newp
+ - table->data)));
+
+ /* Update the statistics. */
+ if (packet->notfound)
+ ++table->head->negmiss;
+ else if (first)
+ ++table->head->posmiss;
+
+ /* We depend on this value being correct and at least as high as the
+ real number of entries. */
+ atomic_increment (&table->head->nentries);
+
+ /* It does not matter that we are not loading the just increment
+ value, this is just for statistics. */
+ unsigned long int nentries = table->head->nentries;
+ if (nentries > table->head->maxnentries)
+ table->head->maxnentries = nentries;
+
+ if (table->persistent)
+ // XXX async OK?
+ msync ((void *) table->head,
+ (char *) &table->head->array[hash] - (char *) table->head
+ + sizeof (ref_t), MS_ASYNC);
+
+ /* We do not have to worry about the pruning thread if we are
+ re-adding the data since this is done by the pruning thread. We
+ also do not have to do anything in case this is not the first
+ time the data is entered since different data heads all have the
+ same timeout. */
+ if (first && prune_wakeup)
+ {
+ /* Perhaps the prune thread for the table is not running in a long
+ time. Wake it if necessary. */
+ pthread_mutex_lock (&table->prune_lock);
+ time_t next_wakeup = table->wakeup_time;
+ bool do_wakeup = false;
+ if (next_wakeup > packet->timeout + CACHE_PRUNE_INTERVAL)
+ {
+ table->wakeup_time = packet->timeout;
+ do_wakeup = true;
+ }
+ pthread_mutex_unlock (&table->prune_lock);
+ if (do_wakeup)
+ pthread_cond_signal (&table->prune_cond);
+ }
+
+ return 0;
+}
+
+/* Walk through the table and remove all entries which lifetime ended.
+
+ We have a problem here. To actually remove the entries we must get
+ the write-lock. But since we want to keep the time we have the
+ lock as short as possible we cannot simply acquire the lock when we
+ start looking for timedout entries.
+
+ Therefore we do it in two stages: first we look for entries which
+ must be invalidated and remember them. Then we get the lock and
+ actually remove them. This is complicated by the way we have to
+ free the data structures since some hash table entries share the same
+ data. */
+time_t
+prune_cache (struct database_dyn *table, time_t now, int fd)
+{
+ size_t cnt = table->head->module;
+
+ /* If this table is not actually used don't do anything. */
+ if (cnt == 0)
+ {
+ if (fd != -1)
+ {
+ /* Reply to the INVALIDATE initiator. */
+ int32_t resp = 0;
+ writeall (fd, &resp, sizeof (resp));
+ }
+
+ /* No need to do this again anytime soon. */
+ return 24 * 60 * 60;
+ }
+
+ /* If we check for the modification of the underlying file we invalidate
+ the entries also in this case. */
+ if (table->check_file && now != LONG_MAX)
+ {
+ struct traced_file *runp = table->traced_files;
+
+ while (runp != NULL)
+ {
+#ifdef HAVE_INOTIFY
+ if (runp->inotify_descr[TRACED_FILE] == -1)
+#endif
+ {
+ struct stat64 st;
+
+ if (stat64 (runp->fname, &st) < 0)
+ {
+ /* Print a diagnostic that the traced file was missing.
+ We must not disable tracing since the file might return
+ shortly and we want to reload it at the next pruning.
+ Disabling tracing here would go against the configuration
+ as specified by the user via check-files. */
+ char buf[128];
+ dbg_log (_("checking for monitored file `%s': %s"),
+ runp->fname, strerror_r (errno, buf, sizeof (buf)));
+ }
+ else
+ {
+ /* This must be `!=` to catch cases where users turn the
+ clocks back and we still want to detect any time difference
+ in mtime. */
+ if (st.st_mtime != runp->mtime)
+ {
+ dbg_log (_("monitored file `%s` changed (mtime)"),
+ runp->fname);
+ /* The file changed. Invalidate all entries. */
+ now = LONG_MAX;
+ runp->mtime = st.st_mtime;
+#ifdef HAVE_INOTIFY
+ /* Attempt to install a watch on the file. */
+ install_watches (runp);
+#endif
+ }
+ }
+ }
+
+ runp = runp->next;
+ }
+ }
+
+ /* We run through the table and find values which are not valid anymore.
+
+ Note that for the initial step, finding the entries to be removed,
+ we don't need to get any lock. It is at all timed assured that the
+ linked lists are set up correctly and that no second thread prunes
+ the cache. */
+ bool *mark;
+ size_t memory_needed = cnt * sizeof (bool);
+ bool mark_use_alloca;
+ if (__glibc_likely (memory_needed <= MAX_STACK_USE))
+ {
+ mark = alloca (cnt * sizeof (bool));
+ memset (mark, '\0', memory_needed);
+ mark_use_alloca = true;
+ }
+ else
+ {
+ mark = xcalloc (1, memory_needed);
+ mark_use_alloca = false;
+ }
+ size_t first = cnt + 1;
+ size_t last = 0;
+ char *const data = table->data;
+ bool any = false;
+
+ if (__glibc_unlikely (debug_level > 2))
+ dbg_log (_("pruning %s cache; time %ld"),
+ dbnames[table - dbs], (long int) now);
+
+#define NO_TIMEOUT LONG_MAX
+ time_t next_timeout = NO_TIMEOUT;
+ do
+ {
+ ref_t run = table->head->array[--cnt];
+
+ while (run != ENDREF)
+ {
+ struct hashentry *runp = (struct hashentry *) (data + run);
+ struct datahead *dh = (struct datahead *) (data + runp->packet);
+
+ /* Some debug support. */
+ if (__glibc_unlikely (debug_level > 2))
+ {
+ char buf[INET6_ADDRSTRLEN];
+ const char *str;
+
+ if (runp->type == GETHOSTBYADDR || runp->type == GETHOSTBYADDRv6)
+ {
+ inet_ntop (runp->type == GETHOSTBYADDR ? AF_INET : AF_INET6,
+ data + runp->key, buf, sizeof (buf));
+ str = buf;
+ }
+ else
+ str = data + runp->key;
+
+ dbg_log (_("considering %s entry \"%s\", timeout %" PRIu64),
+ serv2str[runp->type], str, dh->timeout);
+ }
+
+ /* Check whether the entry timed out. */
+ if (dh->timeout < now)
+ {
+ /* This hash bucket could contain entries which need to
+ be looked at. */
+ mark[cnt] = true;
+
+ first = MIN (first, cnt);
+ last = MAX (last, cnt);
+
+ /* We only have to look at the data of the first entries
+ since the count information is kept in the data part
+ which is shared. */
+ if (runp->first)
+ {
+
+ /* At this point there are two choices: we reload the
+ value or we discard it. Do not change NRELOADS if
+ we never not reload the record. */
+ if ((reload_count != UINT_MAX
+ && __builtin_expect (dh->nreloads >= reload_count, 0))
+ /* We always remove negative entries. */
+ || dh->notfound
+ /* Discard everything if the user explicitly
+ requests it. */
+ || now == LONG_MAX)
+ {
+ /* Remove the value. */
+ dh->usable = false;
+
+ /* We definitely have some garbage entries now. */
+ any = true;
+ }
+ else
+ {
+ /* Reload the value. We do this only for the
+ initially used key, not the additionally
+ added derived value. */
+ assert (runp->type < LASTREQ
+ && readdfcts[runp->type] != NULL);
+
+ time_t timeout = readdfcts[runp->type] (table, runp, dh);
+ next_timeout = MIN (next_timeout, timeout);
+
+ /* If the entry has been replaced, we might need
+ cleanup. */
+ any |= !dh->usable;
+ }
+ }
+ }
+ else
+ {
+ assert (dh->usable);
+ next_timeout = MIN (next_timeout, dh->timeout);
+ }
+
+ run = runp->next;
+ }
+ }
+ while (cnt > 0);
+
+ if (__glibc_unlikely (fd != -1))
+ {
+ /* Reply to the INVALIDATE initiator that the cache has been
+ invalidated. */
+ int32_t resp = 0;
+ writeall (fd, &resp, sizeof (resp));
+ }
+
+ if (first <= last)
+ {
+ struct hashentry *head = NULL;
+
+ /* Now we have to get the write lock since we are about to modify
+ the table. */
+ if (__glibc_unlikely (pthread_rwlock_trywrlock (&table->lock) != 0))
+ {
+ ++table->head->wrlockdelayed;
+ pthread_rwlock_wrlock (&table->lock);
+ }
+
+ while (first <= last)
+ {
+ if (mark[first])
+ {
+ ref_t *old = &table->head->array[first];
+ ref_t run = table->head->array[first];
+
+ assert (run != ENDREF);
+ do
+ {
+ struct hashentry *runp = (struct hashentry *) (data + run);
+ struct datahead *dh
+ = (struct datahead *) (data + runp->packet);
+
+ if (! dh->usable)
+ {
+ /* We need the list only for debugging but it is
+ more costly to avoid creating the list than
+ doing it. */
+ runp->dellist = head;
+ head = runp;
+
+ /* No need for an atomic operation, we have the
+ write lock. */
+ --table->head->nentries;
+
+ run = *old = runp->next;
+ }
+ else
+ {
+ old = &runp->next;
+ run = runp->next;
+ }
+ }
+ while (run != ENDREF);
+ }
+
+ ++first;
+ }
+
+ /* It's all done. */
+ pthread_rwlock_unlock (&table->lock);
+
+ /* Make sure the data is saved to disk. */
+ if (table->persistent)
+ msync (table->head,
+ data + table->head->first_free - (char *) table->head,
+ MS_ASYNC);
+
+ /* One extra pass if we do debugging. */
+ if (__glibc_unlikely (debug_level > 0))
+ {
+ struct hashentry *runp = head;
+
+ while (runp != NULL)
+ {
+ char buf[INET6_ADDRSTRLEN];
+ const char *str;
+
+ if (runp->type == GETHOSTBYADDR || runp->type == GETHOSTBYADDRv6)
+ {
+ inet_ntop (runp->type == GETHOSTBYADDR ? AF_INET : AF_INET6,
+ data + runp->key, buf, sizeof (buf));
+ str = buf;
+ }
+ else
+ str = data + runp->key;
+
+ dbg_log ("remove %s entry \"%s\"", serv2str[runp->type], str);
+
+ runp = runp->dellist;
+ }
+ }
+ }
+
+ if (__glibc_unlikely (! mark_use_alloca))
+ free (mark);
+
+ /* Run garbage collection if any entry has been removed or replaced. */
+ if (any)
+ gc (table);
+
+ /* If there is no entry in the database and we therefore have no new
+ timeout value, tell the caller to wake up in 24 hours. */
+ return next_timeout == NO_TIMEOUT ? 24 * 60 * 60 : next_timeout - now;
+}
diff --git a/REORG.TODO/nscd/connections.c b/REORG.TODO/nscd/connections.c
new file mode 100644
index 0000000000..cc1ed72077
--- /dev/null
+++ b/REORG.TODO/nscd/connections.c
@@ -0,0 +1,2558 @@
+/* Inner loops of cache daemon.
+ Copyright (C) 1998-2017 Free Software Foundation, Inc.
+ This file is part of the GNU C Library.
+ Contributed by Ulrich Drepper <drepper@cygnus.com>, 1998.
+
+ 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 <atomic.h>
+#include <error.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <grp.h>
+#include <ifaddrs.h>
+#include <libintl.h>
+#include <pthread.h>
+#include <pwd.h>
+#include <resolv.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdint.h>
+#include <arpa/inet.h>
+#ifdef HAVE_NETLINK
+# include <linux/netlink.h>
+# include <linux/rtnetlink.h>
+#endif
+#ifdef HAVE_EPOLL
+# include <sys/epoll.h>
+#endif
+#ifdef HAVE_INOTIFY
+# include <sys/inotify.h>
+#endif
+#include <sys/mman.h>
+#include <sys/param.h>
+#include <sys/poll.h>
+#ifdef HAVE_SENDFILE
+# include <sys/sendfile.h>
+#endif
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/un.h>
+
+#include "nscd.h"
+#include "dbg_log.h"
+#include "selinux.h"
+#include <resolv/resolv.h>
+
+#include <kernel-features.h>
+#include <libc-diag.h>
+
+
+/* Support to run nscd as an unprivileged user */
+const char *server_user;
+static uid_t server_uid;
+static gid_t server_gid;
+const char *stat_user;
+uid_t stat_uid;
+static gid_t *server_groups;
+#ifndef NGROUPS
+# define NGROUPS 32
+#endif
+static int server_ngroups;
+
+static pthread_attr_t attr;
+
+static void begin_drop_privileges (void);
+static void finish_drop_privileges (void);
+
+/* Map request type to a string. */
+const char *const serv2str[LASTREQ] =
+{
+ [GETPWBYNAME] = "GETPWBYNAME",
+ [GETPWBYUID] = "GETPWBYUID",
+ [GETGRBYNAME] = "GETGRBYNAME",
+ [GETGRBYGID] = "GETGRBYGID",
+ [GETHOSTBYNAME] = "GETHOSTBYNAME",
+ [GETHOSTBYNAMEv6] = "GETHOSTBYNAMEv6",
+ [GETHOSTBYADDR] = "GETHOSTBYADDR",
+ [GETHOSTBYADDRv6] = "GETHOSTBYADDRv6",
+ [SHUTDOWN] = "SHUTDOWN",
+ [GETSTAT] = "GETSTAT",
+ [INVALIDATE] = "INVALIDATE",
+ [GETFDPW] = "GETFDPW",
+ [GETFDGR] = "GETFDGR",
+ [GETFDHST] = "GETFDHST",
+ [GETAI] = "GETAI",
+ [INITGROUPS] = "INITGROUPS",
+ [GETSERVBYNAME] = "GETSERVBYNAME",
+ [GETSERVBYPORT] = "GETSERVBYPORT",
+ [GETFDSERV] = "GETFDSERV",
+ [GETNETGRENT] = "GETNETGRENT",
+ [INNETGR] = "INNETGR",
+ [GETFDNETGR] = "GETFDNETGR"
+};
+
+/* The control data structures for the services. */
+struct database_dyn dbs[lastdb] =
+{
+ [pwddb] = {
+ .lock = PTHREAD_RWLOCK_WRITER_NONRECURSIVE_INITIALIZER_NP,
+ .prune_lock = PTHREAD_MUTEX_INITIALIZER,
+ .prune_run_lock = PTHREAD_MUTEX_INITIALIZER,
+ .enabled = 0,
+ .check_file = 1,
+ .persistent = 0,
+ .propagate = 1,
+ .shared = 0,
+ .max_db_size = DEFAULT_MAX_DB_SIZE,
+ .suggested_module = DEFAULT_SUGGESTED_MODULE,
+ .db_filename = _PATH_NSCD_PASSWD_DB,
+ .disabled_iov = &pwd_iov_disabled,
+ .postimeout = 3600,
+ .negtimeout = 20,
+ .wr_fd = -1,
+ .ro_fd = -1,
+ .mmap_used = false
+ },
+ [grpdb] = {
+ .lock = PTHREAD_RWLOCK_WRITER_NONRECURSIVE_INITIALIZER_NP,
+ .prune_lock = PTHREAD_MUTEX_INITIALIZER,
+ .prune_run_lock = PTHREAD_MUTEX_INITIALIZER,
+ .enabled = 0,
+ .check_file = 1,
+ .persistent = 0,
+ .propagate = 1,
+ .shared = 0,
+ .max_db_size = DEFAULT_MAX_DB_SIZE,
+ .suggested_module = DEFAULT_SUGGESTED_MODULE,
+ .db_filename = _PATH_NSCD_GROUP_DB,
+ .disabled_iov = &grp_iov_disabled,
+ .postimeout = 3600,
+ .negtimeout = 60,
+ .wr_fd = -1,
+ .ro_fd = -1,
+ .mmap_used = false
+ },
+ [hstdb] = {
+ .lock = PTHREAD_RWLOCK_WRITER_NONRECURSIVE_INITIALIZER_NP,
+ .prune_lock = PTHREAD_MUTEX_INITIALIZER,
+ .prune_run_lock = PTHREAD_MUTEX_INITIALIZER,
+ .enabled = 0,
+ .check_file = 1,
+ .persistent = 0,
+ .propagate = 0, /* Not used. */
+ .shared = 0,
+ .max_db_size = DEFAULT_MAX_DB_SIZE,
+ .suggested_module = DEFAULT_SUGGESTED_MODULE,
+ .db_filename = _PATH_NSCD_HOSTS_DB,
+ .disabled_iov = &hst_iov_disabled,
+ .postimeout = 3600,
+ .negtimeout = 20,
+ .wr_fd = -1,
+ .ro_fd = -1,
+ .mmap_used = false
+ },
+ [servdb] = {
+ .lock = PTHREAD_RWLOCK_WRITER_NONRECURSIVE_INITIALIZER_NP,
+ .prune_lock = PTHREAD_MUTEX_INITIALIZER,
+ .prune_run_lock = PTHREAD_MUTEX_INITIALIZER,
+ .enabled = 0,
+ .check_file = 1,
+ .persistent = 0,
+ .propagate = 0, /* Not used. */
+ .shared = 0,
+ .max_db_size = DEFAULT_MAX_DB_SIZE,
+ .suggested_module = DEFAULT_SUGGESTED_MODULE,
+ .db_filename = _PATH_NSCD_SERVICES_DB,
+ .disabled_iov = &serv_iov_disabled,
+ .postimeout = 28800,
+ .negtimeout = 20,
+ .wr_fd = -1,
+ .ro_fd = -1,
+ .mmap_used = false
+ },
+ [netgrdb] = {
+ .lock = PTHREAD_RWLOCK_WRITER_NONRECURSIVE_INITIALIZER_NP,
+ .prune_lock = PTHREAD_MUTEX_INITIALIZER,
+ .prune_run_lock = PTHREAD_MUTEX_INITIALIZER,
+ .enabled = 0,
+ .check_file = 1,
+ .persistent = 0,
+ .propagate = 0, /* Not used. */
+ .shared = 0,
+ .max_db_size = DEFAULT_MAX_DB_SIZE,
+ .suggested_module = DEFAULT_SUGGESTED_MODULE,
+ .db_filename = _PATH_NSCD_NETGROUP_DB,
+ .disabled_iov = &netgroup_iov_disabled,
+ .postimeout = 28800,
+ .negtimeout = 20,
+ .wr_fd = -1,
+ .ro_fd = -1,
+ .mmap_used = false
+ }
+};
+
+
+/* Mapping of request type to database. */
+static struct
+{
+ bool data_request;
+ struct database_dyn *db;
+} const reqinfo[LASTREQ] =
+{
+ [GETPWBYNAME] = { true, &dbs[pwddb] },
+ [GETPWBYUID] = { true, &dbs[pwddb] },
+ [GETGRBYNAME] = { true, &dbs[grpdb] },
+ [GETGRBYGID] = { true, &dbs[grpdb] },
+ [GETHOSTBYNAME] = { true, &dbs[hstdb] },
+ [GETHOSTBYNAMEv6] = { true, &dbs[hstdb] },
+ [GETHOSTBYADDR] = { true, &dbs[hstdb] },
+ [GETHOSTBYADDRv6] = { true, &dbs[hstdb] },
+ [SHUTDOWN] = { false, NULL },
+ [GETSTAT] = { false, NULL },
+ [SHUTDOWN] = { false, NULL },
+ [GETFDPW] = { false, &dbs[pwddb] },
+ [GETFDGR] = { false, &dbs[grpdb] },
+ [GETFDHST] = { false, &dbs[hstdb] },
+ [GETAI] = { true, &dbs[hstdb] },
+ [INITGROUPS] = { true, &dbs[grpdb] },
+ [GETSERVBYNAME] = { true, &dbs[servdb] },
+ [GETSERVBYPORT] = { true, &dbs[servdb] },
+ [GETFDSERV] = { false, &dbs[servdb] },
+ [GETNETGRENT] = { true, &dbs[netgrdb] },
+ [INNETGR] = { true, &dbs[netgrdb] },
+ [GETFDNETGR] = { false, &dbs[netgrdb] }
+};
+
+
+/* Initial number of threads to use. */
+int nthreads = -1;
+/* Maximum number of threads to use. */
+int max_nthreads = 32;
+
+/* Socket for incoming connections. */
+static int sock;
+
+#ifdef HAVE_INOTIFY
+/* Inotify descriptor. */
+int inotify_fd = -1;
+#endif
+
+#ifdef HAVE_NETLINK
+/* Descriptor for netlink status updates. */
+static int nl_status_fd = -1;
+#endif
+
+/* Number of times clients had to wait. */
+unsigned long int client_queued;
+
+
+ssize_t
+writeall (int fd, const void *buf, size_t len)
+{
+ size_t n = len;
+ ssize_t ret;
+ do
+ {
+ ret = TEMP_FAILURE_RETRY (send (fd, buf, n, MSG_NOSIGNAL));
+ if (ret <= 0)
+ break;
+ buf = (const char *) buf + ret;
+ n -= ret;
+ }
+ while (n > 0);
+ return ret < 0 ? ret : len - n;
+}
+
+
+#ifdef HAVE_SENDFILE
+ssize_t
+sendfileall (int tofd, int fromfd, off_t off, size_t len)
+{
+ ssize_t n = len;
+ ssize_t ret;
+
+ do
+ {
+ ret = TEMP_FAILURE_RETRY (sendfile (tofd, fromfd, &off, n));
+ if (ret <= 0)
+ break;
+ n -= ret;
+ }
+ while (n > 0);
+ return ret < 0 ? ret : len - n;
+}
+#endif
+
+
+enum usekey
+ {
+ use_not = 0,
+ /* The following three are not really used, they are symbolic constants. */
+ use_first = 16,
+ use_begin = 32,
+ use_end = 64,
+
+ use_he = 1,
+ use_he_begin = use_he | use_begin,
+ use_he_end = use_he | use_end,
+ use_data = 3,
+ use_data_begin = use_data | use_begin,
+ use_data_end = use_data | use_end,
+ use_data_first = use_data_begin | use_first
+ };
+
+
+static int
+check_use (const char *data, nscd_ssize_t first_free, uint8_t *usemap,
+ enum usekey use, ref_t start, size_t len)
+{
+ assert (len >= 2);
+
+ if (start > first_free || start + len > first_free
+ || (start & BLOCK_ALIGN_M1))
+ return 0;
+
+ if (usemap[start] == use_not)
+ {
+ /* Add the start marker. */
+ usemap[start] = use | use_begin;
+ use &= ~use_first;
+
+ while (--len > 0)
+ if (usemap[++start] != use_not)
+ return 0;
+ else
+ usemap[start] = use;
+
+ /* Add the end marker. */
+ usemap[start] = use | use_end;
+ }
+ else if ((usemap[start] & ~use_first) == ((use | use_begin) & ~use_first))
+ {
+ /* Hash entries can't be shared. */
+ if (use == use_he)
+ return 0;
+
+ usemap[start] |= (use & use_first);
+ use &= ~use_first;
+
+ while (--len > 1)
+ if (usemap[++start] != use)
+ return 0;
+
+ if (usemap[++start] != (use | use_end))
+ return 0;
+ }
+ else
+ /* Points to a wrong object or somewhere in the middle. */
+ return 0;
+
+ return 1;
+}
+
+
+/* Verify data in persistent database. */
+static int
+verify_persistent_db (void *mem, struct database_pers_head *readhead, int dbnr)
+{
+ assert (dbnr == pwddb || dbnr == grpdb || dbnr == hstdb || dbnr == servdb
+ || dbnr == netgrdb);
+
+ time_t now = time (NULL);
+
+ struct database_pers_head *head = mem;
+ struct database_pers_head head_copy = *head;
+
+ /* Check that the header that was read matches the head in the database. */
+ if (memcmp (head, readhead, sizeof (*head)) != 0)
+ return 0;
+
+ /* First some easy tests: make sure the database header is sane. */
+ if (head->version != DB_VERSION
+ || head->header_size != sizeof (*head)
+ /* We allow a timestamp to be one hour ahead of the current time.
+ This should cover daylight saving time changes. */
+ || head->timestamp > now + 60 * 60 + 60
+ || (head->gc_cycle & 1)
+ || head->module == 0
+ || (size_t) head->module > INT32_MAX / sizeof (ref_t)
+ || (size_t) head->data_size > INT32_MAX - head->module * sizeof (ref_t)
+ || head->first_free < 0
+ || head->first_free > head->data_size
+ || (head->first_free & BLOCK_ALIGN_M1) != 0
+ || head->maxnentries < 0
+ || head->maxnsearched < 0)
+ return 0;
+
+ uint8_t *usemap = calloc (head->first_free, 1);
+ if (usemap == NULL)
+ return 0;
+
+ const char *data = (char *) &head->array[roundup (head->module,
+ ALIGN / sizeof (ref_t))];
+
+ nscd_ssize_t he_cnt = 0;
+ for (nscd_ssize_t cnt = 0; cnt < head->module; ++cnt)
+ {
+ ref_t trail = head->array[cnt];
+ ref_t work = trail;
+ int tick = 0;
+
+ while (work != ENDREF)
+ {
+ if (! check_use (data, head->first_free, usemap, use_he, work,
+ sizeof (struct hashentry)))
+ goto fail;
+
+ /* Now we know we can dereference the record. */
+ struct hashentry *here = (struct hashentry *) (data + work);
+
+ ++he_cnt;
+
+ /* Make sure the record is for this type of service. */
+ if (here->type >= LASTREQ
+ || reqinfo[here->type].db != &dbs[dbnr])
+ goto fail;
+
+ /* Validate boolean field value. */
+ if (here->first != false && here->first != true)
+ goto fail;
+
+ if (here->len < 0)
+ goto fail;
+
+ /* Now the data. */
+ if (here->packet < 0
+ || here->packet > head->first_free
+ || here->packet + sizeof (struct datahead) > head->first_free)
+ goto fail;
+
+ struct datahead *dh = (struct datahead *) (data + here->packet);
+
+ if (! check_use (data, head->first_free, usemap,
+ use_data | (here->first ? use_first : 0),
+ here->packet, dh->allocsize))
+ goto fail;
+
+ if (dh->allocsize < sizeof (struct datahead)
+ || dh->recsize > dh->allocsize
+ || (dh->notfound != false && dh->notfound != true)
+ || (dh->usable != false && dh->usable != true))
+ goto fail;
+
+ if (here->key < here->packet + sizeof (struct datahead)
+ || here->key > here->packet + dh->allocsize
+ || here->key + here->len > here->packet + dh->allocsize)
+ goto fail;
+
+ work = here->next;
+
+ if (work == trail)
+ /* A circular list, this must not happen. */
+ goto fail;
+ if (tick)
+ trail = ((struct hashentry *) (data + trail))->next;
+ tick = 1 - tick;
+ }
+ }
+
+ if (he_cnt != head->nentries)
+ goto fail;
+
+ /* See if all data and keys had at least one reference from
+ he->first == true hashentry. */
+ for (ref_t idx = 0; idx < head->first_free; ++idx)
+ {
+ if (usemap[idx] == use_data_begin)
+ goto fail;
+ }
+
+ /* Finally, make sure the database hasn't changed since the first test. */
+ if (memcmp (mem, &head_copy, sizeof (*head)) != 0)
+ goto fail;
+
+ free (usemap);
+ return 1;
+
+fail:
+ free (usemap);
+ return 0;
+}
+
+
+/* Initialize database information structures. */
+void
+nscd_init (void)
+{
+ /* Look up unprivileged uid/gid/groups before we start listening on the
+ socket */
+ if (server_user != NULL)
+ begin_drop_privileges ();
+
+ if (nthreads == -1)
+ /* No configuration for this value, assume a default. */
+ nthreads = 4;
+
+ for (size_t cnt = 0; cnt < lastdb; ++cnt)
+ if (dbs[cnt].enabled)
+ {
+ pthread_rwlock_init (&dbs[cnt].lock, NULL);
+ pthread_mutex_init (&dbs[cnt].memlock, NULL);
+
+ if (dbs[cnt].persistent)
+ {
+ /* Try to open the appropriate file on disk. */
+ int fd = open (dbs[cnt].db_filename, O_RDWR | O_CLOEXEC);
+ if (fd != -1)
+ {
+ char *msg = NULL;
+ struct stat64 st;
+ void *mem;
+ size_t total;
+ struct database_pers_head head;
+ ssize_t n = TEMP_FAILURE_RETRY (read (fd, &head,
+ sizeof (head)));
+ if (n != sizeof (head) || fstat64 (fd, &st) != 0)
+ {
+ fail_db_errno:
+ /* The code is single-threaded at this point so
+ using strerror is just fine. */
+ msg = strerror (errno);
+ fail_db:
+ dbg_log (_("invalid persistent database file \"%s\": %s"),
+ dbs[cnt].db_filename, msg);
+ unlink (dbs[cnt].db_filename);
+ }
+ else if (head.module == 0 && head.data_size == 0)
+ {
+ /* The file has been created, but the head has not
+ been initialized yet. */
+ msg = _("uninitialized header");
+ goto fail_db;
+ }
+ else if (head.header_size != (int) sizeof (head))
+ {
+ msg = _("header size does not match");
+ goto fail_db;
+ }
+ else if ((total = (sizeof (head)
+ + roundup (head.module * sizeof (ref_t),
+ ALIGN)
+ + head.data_size))
+ > st.st_size
+ || total < sizeof (head))
+ {
+ msg = _("file size does not match");
+ goto fail_db;
+ }
+ /* Note we map with the maximum size allowed for the
+ database. This is likely much larger than the
+ actual file size. This is OK on most OSes since
+ extensions of the underlying file will
+ automatically translate more pages available for
+ memory access. */
+ else if ((mem = mmap (NULL, dbs[cnt].max_db_size,
+ PROT_READ | PROT_WRITE,
+ MAP_SHARED, fd, 0))
+ == MAP_FAILED)
+ goto fail_db_errno;
+ else if (!verify_persistent_db (mem, &head, cnt))
+ {
+ munmap (mem, total);
+ msg = _("verification failed");
+ goto fail_db;
+ }
+ else
+ {
+ /* Success. We have the database. */
+ dbs[cnt].head = mem;
+ dbs[cnt].memsize = total;
+ dbs[cnt].data = (char *)
+ &dbs[cnt].head->array[roundup (dbs[cnt].head->module,
+ ALIGN / sizeof (ref_t))];
+ dbs[cnt].mmap_used = true;
+
+ if (dbs[cnt].suggested_module > head.module)
+ dbg_log (_("suggested size of table for database %s larger than the persistent database's table"),
+ dbnames[cnt]);
+
+ dbs[cnt].wr_fd = fd;
+ fd = -1;
+ /* We also need a read-only descriptor. */
+ if (dbs[cnt].shared)
+ {
+ dbs[cnt].ro_fd = open (dbs[cnt].db_filename,
+ O_RDONLY | O_CLOEXEC);
+ if (dbs[cnt].ro_fd == -1)
+ dbg_log (_("\
+cannot create read-only descriptor for \"%s\"; no mmap"),
+ dbs[cnt].db_filename);
+ }
+
+ // XXX Shall we test whether the descriptors actually
+ // XXX point to the same file?
+ }
+
+ /* Close the file descriptors in case something went
+ wrong in which case the variable have not been
+ assigned -1. */
+ if (fd != -1)
+ close (fd);
+ }
+ else if (errno == EACCES)
+ do_exit (EXIT_FAILURE, 0, _("cannot access '%s'"),
+ dbs[cnt].db_filename);
+ }
+
+ if (dbs[cnt].head == NULL)
+ {
+ /* No database loaded. Allocate the data structure,
+ possibly on disk. */
+ struct database_pers_head head;
+ size_t total = (sizeof (head)
+ + roundup (dbs[cnt].suggested_module
+ * sizeof (ref_t), ALIGN)
+ + (dbs[cnt].suggested_module
+ * DEFAULT_DATASIZE_PER_BUCKET));
+
+ /* Try to create the database. If we do not need a
+ persistent database create a temporary file. */
+ int fd;
+ int ro_fd = -1;
+ if (dbs[cnt].persistent)
+ {
+ fd = open (dbs[cnt].db_filename,
+ O_RDWR | O_CREAT | O_EXCL | O_TRUNC | O_CLOEXEC,
+ S_IRUSR | S_IWUSR);
+ if (fd != -1 && dbs[cnt].shared)
+ ro_fd = open (dbs[cnt].db_filename,
+ O_RDONLY | O_CLOEXEC);
+ }
+ else
+ {
+ char fname[] = _PATH_NSCD_XYZ_DB_TMP;
+ fd = mkostemp (fname, O_CLOEXEC);
+
+ /* We do not need the file name anymore after we
+ opened another file descriptor in read-only mode. */
+ if (fd != -1)
+ {
+ if (dbs[cnt].shared)
+ ro_fd = open (fname, O_RDONLY | O_CLOEXEC);
+
+ unlink (fname);
+ }
+ }
+
+ if (fd == -1)
+ {
+ if (errno == EEXIST)
+ {
+ dbg_log (_("database for %s corrupted or simultaneously used; remove %s manually if necessary and restart"),
+ dbnames[cnt], dbs[cnt].db_filename);
+ do_exit (1, 0, NULL);
+ }
+
+ if (dbs[cnt].persistent)
+ dbg_log (_("cannot create %s; no persistent database used"),
+ dbs[cnt].db_filename);
+ else
+ dbg_log (_("cannot create %s; no sharing possible"),
+ dbs[cnt].db_filename);
+
+ dbs[cnt].persistent = 0;
+ // XXX remember: no mmap
+ }
+ else
+ {
+ /* Tell the user if we could not create the read-only
+ descriptor. */
+ if (ro_fd == -1 && dbs[cnt].shared)
+ dbg_log (_("\
+cannot create read-only descriptor for \"%s\"; no mmap"),
+ dbs[cnt].db_filename);
+
+ /* Before we create the header, initialize the hash
+ table. That way if we get interrupted while writing
+ the header we can recognize a partially initialized
+ database. */
+ size_t ps = sysconf (_SC_PAGESIZE);
+ char tmpbuf[ps];
+ assert (~ENDREF == 0);
+ memset (tmpbuf, '\xff', ps);
+
+ size_t remaining = dbs[cnt].suggested_module * sizeof (ref_t);
+ off_t offset = sizeof (head);
+
+ size_t towrite;
+ if (offset % ps != 0)
+ {
+ towrite = MIN (remaining, ps - (offset % ps));
+ if (pwrite (fd, tmpbuf, towrite, offset) != towrite)
+ goto write_fail;
+ offset += towrite;
+ remaining -= towrite;
+ }
+
+ while (remaining > ps)
+ {
+ if (pwrite (fd, tmpbuf, ps, offset) == -1)
+ goto write_fail;
+ offset += ps;
+ remaining -= ps;
+ }
+
+ if (remaining > 0
+ && pwrite (fd, tmpbuf, remaining, offset) != remaining)
+ goto write_fail;
+
+ /* Create the header of the file. */
+ struct database_pers_head head =
+ {
+ .version = DB_VERSION,
+ .header_size = sizeof (head),
+ .module = dbs[cnt].suggested_module,
+ .data_size = (dbs[cnt].suggested_module
+ * DEFAULT_DATASIZE_PER_BUCKET),
+ .first_free = 0
+ };
+ void *mem;
+
+ if ((TEMP_FAILURE_RETRY (write (fd, &head, sizeof (head)))
+ != sizeof (head))
+ || (TEMP_FAILURE_RETRY_VAL (posix_fallocate (fd, 0, total))
+ != 0)
+ || (mem = mmap (NULL, dbs[cnt].max_db_size,
+ PROT_READ | PROT_WRITE,
+ MAP_SHARED, fd, 0)) == MAP_FAILED)
+ {
+ write_fail:
+ unlink (dbs[cnt].db_filename);
+ dbg_log (_("cannot write to database file %s: %s"),
+ dbs[cnt].db_filename, strerror (errno));
+ dbs[cnt].persistent = 0;
+ }
+ else
+ {
+ /* Success. */
+ dbs[cnt].head = mem;
+ dbs[cnt].data = (char *)
+ &dbs[cnt].head->array[roundup (dbs[cnt].head->module,
+ ALIGN / sizeof (ref_t))];
+ dbs[cnt].memsize = total;
+ dbs[cnt].mmap_used = true;
+
+ /* Remember the descriptors. */
+ dbs[cnt].wr_fd = fd;
+ dbs[cnt].ro_fd = ro_fd;
+ fd = -1;
+ ro_fd = -1;
+ }
+
+ if (fd != -1)
+ close (fd);
+ if (ro_fd != -1)
+ close (ro_fd);
+ }
+ }
+
+ if (dbs[cnt].head == NULL)
+ {
+ /* We do not use the persistent database. Just
+ create an in-memory data structure. */
+ assert (! dbs[cnt].persistent);
+
+ dbs[cnt].head = xmalloc (sizeof (struct database_pers_head)
+ + (dbs[cnt].suggested_module
+ * sizeof (ref_t)));
+ memset (dbs[cnt].head, '\0', sizeof (struct database_pers_head));
+ assert (~ENDREF == 0);
+ memset (dbs[cnt].head->array, '\xff',
+ dbs[cnt].suggested_module * sizeof (ref_t));
+ dbs[cnt].head->module = dbs[cnt].suggested_module;
+ dbs[cnt].head->data_size = (DEFAULT_DATASIZE_PER_BUCKET
+ * dbs[cnt].head->module);
+ dbs[cnt].data = xmalloc (dbs[cnt].head->data_size);
+ dbs[cnt].head->first_free = 0;
+
+ dbs[cnt].shared = 0;
+ assert (dbs[cnt].ro_fd == -1);
+ }
+ }
+
+ /* Create the socket. */
+ sock = socket (AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0);
+ if (sock < 0)
+ {
+ dbg_log (_("cannot open socket: %s"), strerror (errno));
+ do_exit (errno == EACCES ? 4 : 1, 0, NULL);
+ }
+ /* Bind a name to the socket. */
+ struct sockaddr_un sock_addr;
+ sock_addr.sun_family = AF_UNIX;
+ strcpy (sock_addr.sun_path, _PATH_NSCDSOCKET);
+ if (bind (sock, (struct sockaddr *) &sock_addr, sizeof (sock_addr)) < 0)
+ {
+ dbg_log ("%s: %s", _PATH_NSCDSOCKET, strerror (errno));
+ do_exit (errno == EACCES ? 4 : 1, 0, NULL);
+ }
+
+ /* Set permissions for the socket. */
+ chmod (_PATH_NSCDSOCKET, DEFFILEMODE);
+
+ /* Set the socket up to accept connections. */
+ if (listen (sock, SOMAXCONN) < 0)
+ {
+ dbg_log (_("cannot enable socket to accept connections: %s"),
+ strerror (errno));
+ do_exit (1, 0, NULL);
+ }
+
+#ifdef HAVE_NETLINK
+ if (dbs[hstdb].enabled)
+ {
+ /* Try to open netlink socket to monitor network setting changes. */
+ nl_status_fd = socket (AF_NETLINK,
+ SOCK_RAW | SOCK_CLOEXEC | SOCK_NONBLOCK,
+ NETLINK_ROUTE);
+ if (nl_status_fd != -1)
+ {
+ struct sockaddr_nl snl;
+ memset (&snl, '\0', sizeof (snl));
+ snl.nl_family = AF_NETLINK;
+ /* XXX Is this the best set to use? */
+ snl.nl_groups = (RTMGRP_IPV4_IFADDR | RTMGRP_TC | RTMGRP_IPV4_MROUTE
+ | RTMGRP_IPV4_ROUTE | RTMGRP_IPV4_RULE
+ | RTMGRP_IPV6_IFADDR | RTMGRP_IPV6_MROUTE
+ | RTMGRP_IPV6_ROUTE | RTMGRP_IPV6_IFINFO
+ | RTMGRP_IPV6_PREFIX);
+
+ if (bind (nl_status_fd, (struct sockaddr *) &snl, sizeof (snl)) != 0)
+ {
+ close (nl_status_fd);
+ nl_status_fd = -1;
+ }
+ else
+ {
+ /* Start the timestamp process. */
+ dbs[hstdb].head->extra_data[NSCD_HST_IDX_CONF_TIMESTAMP]
+ = __bump_nl_timestamp ();
+ }
+ }
+ }
+#endif
+
+ /* Change to unprivileged uid/gid/groups if specified in config file */
+ if (server_user != NULL)
+ finish_drop_privileges ();
+}
+
+#ifdef HAVE_INOTIFY
+#define TRACED_FILE_MASK (IN_DELETE_SELF | IN_CLOSE_WRITE | IN_MOVE_SELF)
+#define TRACED_DIR_MASK (IN_DELETE_SELF | IN_CREATE | IN_MOVED_TO | IN_MOVE_SELF)
+void
+install_watches (struct traced_file *finfo)
+{
+ /* Use inotify support if we have it. */
+ if (finfo->inotify_descr[TRACED_FILE] < 0)
+ finfo->inotify_descr[TRACED_FILE] = inotify_add_watch (inotify_fd,
+ finfo->fname,
+ TRACED_FILE_MASK);
+ if (finfo->inotify_descr[TRACED_FILE] < 0)
+ {
+ dbg_log (_("disabled inotify-based monitoring for file `%s': %s"),
+ finfo->fname, strerror (errno));
+ return;
+ }
+ dbg_log (_("monitoring file `%s` (%d)"),
+ finfo->fname, finfo->inotify_descr[TRACED_FILE]);
+ /* Additionally listen for events in the file's parent directory.
+ We do this because the file to be watched might be
+ deleted and then added back again. When it is added back again
+ we must re-add the watch. We must also cover IN_MOVED_TO to
+ detect a file being moved into the directory. */
+ if (finfo->inotify_descr[TRACED_DIR] < 0)
+ finfo->inotify_descr[TRACED_DIR] = inotify_add_watch (inotify_fd,
+ finfo->dname,
+ TRACED_DIR_MASK);
+ if (finfo->inotify_descr[TRACED_DIR] < 0)
+ {
+ dbg_log (_("disabled inotify-based monitoring for directory `%s': %s"),
+ finfo->fname, strerror (errno));
+ return;
+ }
+ dbg_log (_("monitoring directory `%s` (%d)"),
+ finfo->dname, finfo->inotify_descr[TRACED_DIR]);
+}
+#endif
+
+/* Register the file in FINFO as a traced file for the database DBS[DBIX].
+
+ We support registering multiple files per database. Each call to
+ register_traced_file adds to the list of registered files.
+
+ When we prune the database, either through timeout or a request to
+ invalidate, we will check to see if any of the registered files has changed.
+ When we accept new connections to handle a cache request we will also
+ check to see if any of the registered files has changed.
+
+ If we have inotify support then we install an inotify fd to notify us of
+ file deletion or modification, both of which will require we invalidate
+ the cache for the database. Without inotify support we stat the file and
+ store st_mtime to determine if the file has been modified. */
+void
+register_traced_file (size_t dbidx, struct traced_file *finfo)
+{
+ /* If the database is disabled or file checking is disabled
+ then ignore the registration. */
+ if (! dbs[dbidx].enabled || ! dbs[dbidx].check_file)
+ return;
+
+ if (__glibc_unlikely (debug_level > 0))
+ dbg_log (_("monitoring file %s for database %s"),
+ finfo->fname, dbnames[dbidx]);
+
+#ifdef HAVE_INOTIFY
+ install_watches (finfo);
+#endif
+ struct stat64 st;
+ if (stat64 (finfo->fname, &st) < 0)
+ {
+ /* We cannot stat() the file. Set mtime to zero and try again later. */
+ dbg_log (_("stat failed for file `%s'; will try again later: %s"),
+ finfo->fname, strerror (errno));
+ finfo->mtime = 0;
+ }
+ else
+ finfo->mtime = st.st_mtime;
+
+ /* Queue up the file name. */
+ finfo->next = dbs[dbidx].traced_files;
+ dbs[dbidx].traced_files = finfo;
+}
+
+
+/* Close the connections. */
+void
+close_sockets (void)
+{
+ close (sock);
+}
+
+
+static void
+invalidate_cache (char *key, int fd)
+{
+ dbtype number;
+ int32_t resp;
+
+ for (number = pwddb; number < lastdb; ++number)
+ if (strcmp (key, dbnames[number]) == 0)
+ {
+ struct traced_file *runp = dbs[number].traced_files;
+ while (runp != NULL)
+ {
+ /* Make sure we reload from file when checking mtime. */
+ runp->mtime = 0;
+#ifdef HAVE_INOTIFY
+ /* During an invalidation we try to reload the traced
+ file watches. This allows the user to re-sync if
+ inotify events were lost. Similar to what we do during
+ pruning. */
+ install_watches (runp);
+#endif
+ if (runp->call_res_init)
+ {
+ res_init ();
+ break;
+ }
+ runp = runp->next;
+ }
+ break;
+ }
+
+ if (number == lastdb)
+ {
+ resp = EINVAL;
+ writeall (fd, &resp, sizeof (resp));
+ return;
+ }
+
+ if (dbs[number].enabled)
+ {
+ pthread_mutex_lock (&dbs[number].prune_run_lock);
+ prune_cache (&dbs[number], LONG_MAX, fd);
+ pthread_mutex_unlock (&dbs[number].prune_run_lock);
+ }
+ else
+ {
+ resp = 0;
+ writeall (fd, &resp, sizeof (resp));
+ }
+}
+
+
+#ifdef SCM_RIGHTS
+static void
+send_ro_fd (struct database_dyn *db, char *key, int fd)
+{
+ /* If we do not have an read-only file descriptor do nothing. */
+ if (db->ro_fd == -1)
+ return;
+
+ /* We need to send some data along with the descriptor. */
+ uint64_t mapsize = (db->head->data_size
+ + roundup (db->head->module * sizeof (ref_t), ALIGN)
+ + sizeof (struct database_pers_head));
+ struct iovec iov[2];
+ iov[0].iov_base = key;
+ iov[0].iov_len = strlen (key) + 1;
+ iov[1].iov_base = &mapsize;
+ iov[1].iov_len = sizeof (mapsize);
+
+ /* Prepare the control message to transfer the descriptor. */
+ union
+ {
+ struct cmsghdr hdr;
+ char bytes[CMSG_SPACE (sizeof (int))];
+ } buf;
+ struct msghdr msg = { .msg_iov = iov, .msg_iovlen = 2,
+ .msg_control = buf.bytes,
+ .msg_controllen = sizeof (buf) };
+ struct cmsghdr *cmsg = CMSG_FIRSTHDR (&msg);
+
+ cmsg->cmsg_level = SOL_SOCKET;
+ cmsg->cmsg_type = SCM_RIGHTS;
+ cmsg->cmsg_len = CMSG_LEN (sizeof (int));
+
+ int *ip = (int *) CMSG_DATA (cmsg);
+ *ip = db->ro_fd;
+
+ msg.msg_controllen = cmsg->cmsg_len;
+
+ /* Send the control message. We repeat when we are interrupted but
+ everything else is ignored. */
+#ifndef MSG_NOSIGNAL
+# define MSG_NOSIGNAL 0
+#endif
+ (void) TEMP_FAILURE_RETRY (sendmsg (fd, &msg, MSG_NOSIGNAL));
+
+ if (__glibc_unlikely (debug_level > 0))
+ dbg_log (_("provide access to FD %d, for %s"), db->ro_fd, key);
+}
+#endif /* SCM_RIGHTS */
+
+
+/* Handle new request. */
+static void
+handle_request (int fd, request_header *req, void *key, uid_t uid, pid_t pid)
+{
+ if (__builtin_expect (req->version, NSCD_VERSION) != NSCD_VERSION)
+ {
+ if (debug_level > 0)
+ dbg_log (_("\
+cannot handle old request version %d; current version is %d"),
+ req->version, NSCD_VERSION);
+ return;
+ }
+
+ /* Perform the SELinux check before we go on to the standard checks. */
+ if (selinux_enabled && nscd_request_avc_has_perm (fd, req->type) != 0)
+ {
+ if (debug_level > 0)
+ {
+#ifdef SO_PEERCRED
+# ifdef PATH_MAX
+ char buf[PATH_MAX];
+# else
+ char buf[4096];
+# endif
+
+ snprintf (buf, sizeof (buf), "/proc/%ld/exe", (long int) pid);
+ ssize_t n = readlink (buf, buf, sizeof (buf) - 1);
+
+ if (n <= 0)
+ dbg_log (_("\
+request from %ld not handled due to missing permission"), (long int) pid);
+ else
+ {
+ buf[n] = '\0';
+ dbg_log (_("\
+request from '%s' [%ld] not handled due to missing permission"),
+ buf, (long int) pid);
+ }
+#else
+ dbg_log (_("request not handled due to missing permission"));
+#endif
+ }
+ return;
+ }
+
+ struct database_dyn *db = reqinfo[req->type].db;
+
+ /* See whether we can service the request from the cache. */
+ if (__builtin_expect (reqinfo[req->type].data_request, true))
+ {
+ if (__builtin_expect (debug_level, 0) > 0)
+ {
+ if (req->type == GETHOSTBYADDR || req->type == GETHOSTBYADDRv6)
+ {
+ char buf[INET6_ADDRSTRLEN];
+
+ dbg_log ("\t%s (%s)", serv2str[req->type],
+ inet_ntop (req->type == GETHOSTBYADDR
+ ? AF_INET : AF_INET6,
+ key, buf, sizeof (buf)));
+ }
+ else
+ dbg_log ("\t%s (%s)", serv2str[req->type], (char *) key);
+ }
+
+ /* Is this service enabled? */
+ if (__glibc_unlikely (!db->enabled))
+ {
+ /* No, sent the prepared record. */
+ if (TEMP_FAILURE_RETRY (send (fd, db->disabled_iov->iov_base,
+ db->disabled_iov->iov_len,
+ MSG_NOSIGNAL))
+ != (ssize_t) db->disabled_iov->iov_len
+ && __builtin_expect (debug_level, 0) > 0)
+ {
+ /* We have problems sending the result. */
+ char buf[256];
+ dbg_log (_("cannot write result: %s"),
+ strerror_r (errno, buf, sizeof (buf)));
+ }
+
+ return;
+ }
+
+ /* Be sure we can read the data. */
+ if (__glibc_unlikely (pthread_rwlock_tryrdlock (&db->lock) != 0))
+ {
+ ++db->head->rdlockdelayed;
+ pthread_rwlock_rdlock (&db->lock);
+ }
+
+ /* See whether we can handle it from the cache. */
+ struct datahead *cached;
+ cached = (struct datahead *) cache_search (req->type, key, req->key_len,
+ db, uid);
+ if (cached != NULL)
+ {
+ /* Hurray it's in the cache. */
+ ssize_t nwritten;
+
+#ifdef HAVE_SENDFILE
+ if (__glibc_likely (db->mmap_used))
+ {
+ assert (db->wr_fd != -1);
+ assert ((char *) cached->data > (char *) db->data);
+ assert ((char *) cached->data - (char *) db->head
+ + cached->recsize
+ <= (sizeof (struct database_pers_head)
+ + db->head->module * sizeof (ref_t)
+ + db->head->data_size));
+ nwritten = sendfileall (fd, db->wr_fd,
+ (char *) cached->data
+ - (char *) db->head, cached->recsize);
+# ifndef __ASSUME_SENDFILE
+ if (nwritten == -1 && errno == ENOSYS)
+ goto use_write;
+# endif
+ }
+ else
+# ifndef __ASSUME_SENDFILE
+ use_write:
+# endif
+#endif
+ nwritten = writeall (fd, cached->data, cached->recsize);
+
+ if (nwritten != cached->recsize
+ && __builtin_expect (debug_level, 0) > 0)
+ {
+ /* We have problems sending the result. */
+ char buf[256];
+ dbg_log (_("cannot write result: %s"),
+ strerror_r (errno, buf, sizeof (buf)));
+ }
+
+ pthread_rwlock_unlock (&db->lock);
+
+ return;
+ }
+
+ pthread_rwlock_unlock (&db->lock);
+ }
+ else if (__builtin_expect (debug_level, 0) > 0)
+ {
+ if (req->type == INVALIDATE)
+ dbg_log ("\t%s (%s)", serv2str[req->type], (char *) key);
+ else
+ dbg_log ("\t%s", serv2str[req->type]);
+ }
+
+ /* Handle the request. */
+ switch (req->type)
+ {
+ case GETPWBYNAME:
+ addpwbyname (db, fd, req, key, uid);
+ break;
+
+ case GETPWBYUID:
+ addpwbyuid (db, fd, req, key, uid);
+ break;
+
+ case GETGRBYNAME:
+ addgrbyname (db, fd, req, key, uid);
+ break;
+
+ case GETGRBYGID:
+ addgrbygid (db, fd, req, key, uid);
+ break;
+
+ case GETHOSTBYNAME:
+ addhstbyname (db, fd, req, key, uid);
+ break;
+
+ case GETHOSTBYNAMEv6:
+ addhstbynamev6 (db, fd, req, key, uid);
+ break;
+
+ case GETHOSTBYADDR:
+ addhstbyaddr (db, fd, req, key, uid);
+ break;
+
+ case GETHOSTBYADDRv6:
+ addhstbyaddrv6 (db, fd, req, key, uid);
+ break;
+
+ case GETAI:
+ addhstai (db, fd, req, key, uid);
+ break;
+
+ case INITGROUPS:
+ addinitgroups (db, fd, req, key, uid);
+ break;
+
+ case GETSERVBYNAME:
+ addservbyname (db, fd, req, key, uid);
+ break;
+
+ case GETSERVBYPORT:
+ addservbyport (db, fd, req, key, uid);
+ break;
+
+ case GETNETGRENT:
+ addgetnetgrent (db, fd, req, key, uid);
+ break;
+
+ case INNETGR:
+ addinnetgr (db, fd, req, key, uid);
+ break;
+
+ case GETSTAT:
+ case SHUTDOWN:
+ case INVALIDATE:
+ {
+ /* Get the callers credentials. */
+#ifdef SO_PEERCRED
+ struct ucred caller;
+ socklen_t optlen = sizeof (caller);
+
+ if (getsockopt (fd, SOL_SOCKET, SO_PEERCRED, &caller, &optlen) < 0)
+ {
+ char buf[256];
+
+ dbg_log (_("error getting caller's id: %s"),
+ strerror_r (errno, buf, sizeof (buf)));
+ break;
+ }
+
+ uid = caller.uid;
+#else
+ /* Some systems have no SO_PEERCRED implementation. They don't
+ care about security so we don't as well. */
+ uid = 0;
+#endif
+ }
+
+ /* Accept shutdown, getstat and invalidate only from root. For
+ the stat call also allow the user specified in the config file. */
+ if (req->type == GETSTAT)
+ {
+ if (uid == 0 || uid == stat_uid)
+ send_stats (fd, dbs);
+ }
+ else if (uid == 0)
+ {
+ if (req->type == INVALIDATE)
+ invalidate_cache (key, fd);
+ else
+ termination_handler (0);
+ }
+ break;
+
+ case GETFDPW:
+ case GETFDGR:
+ case GETFDHST:
+ case GETFDSERV:
+ case GETFDNETGR:
+#ifdef SCM_RIGHTS
+ send_ro_fd (reqinfo[req->type].db, key, fd);
+#endif
+ break;
+
+ default:
+ /* Ignore the command, it's nothing we know. */
+ break;
+ }
+}
+
+
+/* Restart the process. */
+static void
+restart (void)
+{
+ /* First determine the parameters. We do not use the parameters
+ passed to main() since in case nscd is started by running the
+ dynamic linker this will not work. Yes, this is not the usual
+ case but nscd is part of glibc and we occasionally do this. */
+ size_t buflen = 1024;
+ char *buf = alloca (buflen);
+ size_t readlen = 0;
+ int fd = open ("/proc/self/cmdline", O_RDONLY);
+ if (fd == -1)
+ {
+ dbg_log (_("\
+cannot open /proc/self/cmdline: %s; disabling paranoia mode"),
+ strerror (errno));
+
+ paranoia = 0;
+ return;
+ }
+
+ while (1)
+ {
+ ssize_t n = TEMP_FAILURE_RETRY (read (fd, buf + readlen,
+ buflen - readlen));
+ if (n == -1)
+ {
+ dbg_log (_("\
+cannot read /proc/self/cmdline: %s; disabling paranoia mode"),
+ strerror (errno));
+
+ close (fd);
+ paranoia = 0;
+ return;
+ }
+
+ readlen += n;
+
+ if (readlen < buflen)
+ break;
+
+ /* We might have to extend the buffer. */
+ size_t old_buflen = buflen;
+ char *newp = extend_alloca (buf, buflen, 2 * buflen);
+ buf = memmove (newp, buf, old_buflen);
+ }
+
+ close (fd);
+
+ /* Parse the command line. Worst case scenario: every two
+ characters form one parameter (one character plus NUL). */
+ char **argv = alloca ((readlen / 2 + 1) * sizeof (argv[0]));
+ int argc = 0;
+
+ char *cp = buf;
+ while (cp < buf + readlen)
+ {
+ argv[argc++] = cp;
+ cp = (char *) rawmemchr (cp, '\0') + 1;
+ }
+ argv[argc] = NULL;
+
+ /* Second, change back to the old user if we changed it. */
+ if (server_user != NULL)
+ {
+ if (setresuid (old_uid, old_uid, old_uid) != 0)
+ {
+ dbg_log (_("\
+cannot change to old UID: %s; disabling paranoia mode"),
+ strerror (errno));
+
+ paranoia = 0;
+ return;
+ }
+
+ if (setresgid (old_gid, old_gid, old_gid) != 0)
+ {
+ dbg_log (_("\
+cannot change to old GID: %s; disabling paranoia mode"),
+ strerror (errno));
+
+ ignore_value (setuid (server_uid));
+ paranoia = 0;
+ return;
+ }
+ }
+
+ /* Next change back to the old working directory. */
+ if (chdir (oldcwd) == -1)
+ {
+ dbg_log (_("\
+cannot change to old working directory: %s; disabling paranoia mode"),
+ strerror (errno));
+
+ if (server_user != NULL)
+ {
+ ignore_value (setuid (server_uid));
+ ignore_value (setgid (server_gid));
+ }
+ paranoia = 0;
+ return;
+ }
+
+ /* Synchronize memory. */
+ int32_t certainly[lastdb];
+ for (int cnt = 0; cnt < lastdb; ++cnt)
+ if (dbs[cnt].enabled)
+ {
+ /* Make sure nobody keeps using the database. */
+ dbs[cnt].head->timestamp = 0;
+ certainly[cnt] = dbs[cnt].head->nscd_certainly_running;
+ dbs[cnt].head->nscd_certainly_running = 0;
+
+ if (dbs[cnt].persistent)
+ // XXX async OK?
+ msync (dbs[cnt].head, dbs[cnt].memsize, MS_ASYNC);
+ }
+
+ /* The preparations are done. */
+#ifdef PATH_MAX
+ char pathbuf[PATH_MAX];
+#else
+ char pathbuf[256];
+#endif
+ /* Try to exec the real nscd program so the process name (as reported
+ in /proc/PID/status) will be 'nscd', but fall back to /proc/self/exe
+ if readlink or the exec with the result of the readlink call fails. */
+ ssize_t n = readlink ("/proc/self/exe", pathbuf, sizeof (pathbuf) - 1);
+ if (n != -1)
+ {
+ pathbuf[n] = '\0';
+ execv (pathbuf, argv);
+ }
+ execv ("/proc/self/exe", argv);
+
+ /* If we come here, we will never be able to re-exec. */
+ dbg_log (_("re-exec failed: %s; disabling paranoia mode"),
+ strerror (errno));
+
+ if (server_user != NULL)
+ {
+ ignore_value (setuid (server_uid));
+ ignore_value (setgid (server_gid));
+ }
+ if (chdir ("/") != 0)
+ dbg_log (_("cannot change current working directory to \"/\": %s"),
+ strerror (errno));
+ paranoia = 0;
+
+ /* Reenable the databases. */
+ time_t now = time (NULL);
+ for (int cnt = 0; cnt < lastdb; ++cnt)
+ if (dbs[cnt].enabled)
+ {
+ dbs[cnt].head->timestamp = now;
+ dbs[cnt].head->nscd_certainly_running = certainly[cnt];
+ }
+}
+
+
+/* List of file descriptors. */
+struct fdlist
+{
+ int fd;
+ struct fdlist *next;
+};
+/* Memory allocated for the list. */
+static struct fdlist *fdlist;
+/* List of currently ready-to-read file descriptors. */
+static struct fdlist *readylist;
+
+/* Conditional variable and mutex to signal availability of entries in
+ READYLIST. The condvar is initialized dynamically since we might
+ use a different clock depending on availability. */
+static pthread_cond_t readylist_cond = PTHREAD_COND_INITIALIZER;
+static pthread_mutex_t readylist_lock = PTHREAD_MUTEX_INITIALIZER;
+
+/* The clock to use with the condvar. */
+static clockid_t timeout_clock = CLOCK_REALTIME;
+
+/* Number of threads ready to handle the READYLIST. */
+static unsigned long int nready;
+
+
+/* Function for the clean-up threads. */
+static void *
+__attribute__ ((__noreturn__))
+nscd_run_prune (void *p)
+{
+ const long int my_number = (long int) p;
+ assert (dbs[my_number].enabled);
+
+ int dont_need_update = setup_thread (&dbs[my_number]);
+
+ time_t now = time (NULL);
+
+ /* We are running. */
+ dbs[my_number].head->timestamp = now;
+
+ struct timespec prune_ts;
+ if (__glibc_unlikely (clock_gettime (timeout_clock, &prune_ts) == -1))
+ /* Should never happen. */
+ abort ();
+
+ /* Compute the initial timeout time. Prevent all the timers to go
+ off at the same time by adding a db-based value. */
+ prune_ts.tv_sec += CACHE_PRUNE_INTERVAL + my_number;
+ dbs[my_number].wakeup_time = now + CACHE_PRUNE_INTERVAL + my_number;
+
+ pthread_mutex_t *prune_lock = &dbs[my_number].prune_lock;
+ pthread_mutex_t *prune_run_lock = &dbs[my_number].prune_run_lock;
+ pthread_cond_t *prune_cond = &dbs[my_number].prune_cond;
+
+ pthread_mutex_lock (prune_lock);
+ while (1)
+ {
+ /* Wait, but not forever. */
+ int e = 0;
+ if (! dbs[my_number].clear_cache)
+ e = pthread_cond_timedwait (prune_cond, prune_lock, &prune_ts);
+ assert (__builtin_expect (e == 0 || e == ETIMEDOUT, 1));
+
+ time_t next_wait;
+ now = time (NULL);
+ if (e == ETIMEDOUT || now >= dbs[my_number].wakeup_time
+ || dbs[my_number].clear_cache)
+ {
+ /* We will determine the new timout values based on the
+ cache content. Should there be concurrent additions to
+ the cache which are not accounted for in the cache
+ pruning we want to know about it. Therefore set the
+ timeout to the maximum. It will be descreased when adding
+ new entries to the cache, if necessary. */
+ dbs[my_number].wakeup_time = MAX_TIMEOUT_VALUE;
+
+ /* Unconditionally reset the flag. */
+ time_t prune_now = dbs[my_number].clear_cache ? LONG_MAX : now;
+ dbs[my_number].clear_cache = 0;
+
+ pthread_mutex_unlock (prune_lock);
+
+ /* We use a separate lock for running the prune function (instead
+ of keeping prune_lock locked) because this enables concurrent
+ invocations of cache_add which might modify the timeout value. */
+ pthread_mutex_lock (prune_run_lock);
+ next_wait = prune_cache (&dbs[my_number], prune_now, -1);
+ pthread_mutex_unlock (prune_run_lock);
+
+ next_wait = MAX (next_wait, CACHE_PRUNE_INTERVAL);
+ /* If clients cannot determine for sure whether nscd is running
+ we need to wake up occasionally to update the timestamp.
+ Wait 90% of the update period. */
+#define UPDATE_MAPPING_TIMEOUT (MAPPING_TIMEOUT * 9 / 10)
+ if (__glibc_unlikely (! dont_need_update))
+ {
+ next_wait = MIN (UPDATE_MAPPING_TIMEOUT, next_wait);
+ dbs[my_number].head->timestamp = now;
+ }
+
+ pthread_mutex_lock (prune_lock);
+
+ /* Make it known when we will wake up again. */
+ if (now + next_wait < dbs[my_number].wakeup_time)
+ dbs[my_number].wakeup_time = now + next_wait;
+ else
+ next_wait = dbs[my_number].wakeup_time - now;
+ }
+ else
+ /* The cache was just pruned. Do not do it again now. Just
+ use the new timeout value. */
+ next_wait = dbs[my_number].wakeup_time - now;
+
+ if (clock_gettime (timeout_clock, &prune_ts) == -1)
+ /* Should never happen. */
+ abort ();
+
+ /* Compute next timeout time. */
+ prune_ts.tv_sec += next_wait;
+ }
+}
+
+
+/* This is the main loop. It is replicated in different threads but
+ the use of the ready list makes sure only one thread handles an
+ incoming connection. */
+static void *
+__attribute__ ((__noreturn__))
+nscd_run_worker (void *p)
+{
+ char buf[256];
+
+ /* Initial locking. */
+ pthread_mutex_lock (&readylist_lock);
+
+ /* One more thread available. */
+ ++nready;
+
+ while (1)
+ {
+ while (readylist == NULL)
+ pthread_cond_wait (&readylist_cond, &readylist_lock);
+
+ struct fdlist *it = readylist->next;
+ if (readylist->next == readylist)
+ /* Just one entry on the list. */
+ readylist = NULL;
+ else
+ readylist->next = it->next;
+
+ /* Extract the information and mark the record ready to be used
+ again. */
+ int fd = it->fd;
+ it->next = NULL;
+
+ /* One more thread available. */
+ --nready;
+
+ /* We are done with the list. */
+ pthread_mutex_unlock (&readylist_lock);
+
+ /* Now read the request. */
+ request_header req;
+ if (__builtin_expect (TEMP_FAILURE_RETRY (read (fd, &req, sizeof (req)))
+ != sizeof (req), 0))
+ {
+ /* We failed to read data. Note that this also might mean we
+ failed because we would have blocked. */
+ if (debug_level > 0)
+ dbg_log (_("short read while reading request: %s"),
+ strerror_r (errno, buf, sizeof (buf)));
+ goto close_and_out;
+ }
+
+ /* Check whether this is a valid request type. */
+ if (req.type < GETPWBYNAME || req.type >= LASTREQ)
+ goto close_and_out;
+
+ /* Some systems have no SO_PEERCRED implementation. They don't
+ care about security so we don't as well. */
+ uid_t uid = -1;
+#ifdef SO_PEERCRED
+ pid_t pid = 0;
+
+ if (__glibc_unlikely (debug_level > 0))
+ {
+ struct ucred caller;
+ socklen_t optlen = sizeof (caller);
+
+ if (getsockopt (fd, SOL_SOCKET, SO_PEERCRED, &caller, &optlen) == 0)
+ pid = caller.pid;
+ }
+#else
+ const pid_t pid = 0;
+#endif
+
+ /* It should not be possible to crash the nscd with a silly
+ request (i.e., a terribly large key). We limit the size to 1kb. */
+ if (__builtin_expect (req.key_len, 1) < 0
+ || __builtin_expect (req.key_len, 1) > MAXKEYLEN)
+ {
+ if (debug_level > 0)
+ dbg_log (_("key length in request too long: %d"), req.key_len);
+ }
+ else
+ {
+ /* Get the key. */
+ char keybuf[MAXKEYLEN + 1];
+
+ if (__builtin_expect (TEMP_FAILURE_RETRY (read (fd, keybuf,
+ req.key_len))
+ != req.key_len, 0))
+ {
+ /* Again, this can also mean we would have blocked. */
+ if (debug_level > 0)
+ dbg_log (_("short read while reading request key: %s"),
+ strerror_r (errno, buf, sizeof (buf)));
+ goto close_and_out;
+ }
+ keybuf[req.key_len] = '\0';
+
+ if (__builtin_expect (debug_level, 0) > 0)
+ {
+#ifdef SO_PEERCRED
+ if (pid != 0)
+ dbg_log (_("\
+handle_request: request received (Version = %d) from PID %ld"),
+ req.version, (long int) pid);
+ else
+#endif
+ dbg_log (_("\
+handle_request: request received (Version = %d)"), req.version);
+ }
+
+ /* Phew, we got all the data, now process it. */
+ handle_request (fd, &req, keybuf, uid, pid);
+ }
+
+ close_and_out:
+ /* We are done. */
+ close (fd);
+
+ /* Re-locking. */
+ pthread_mutex_lock (&readylist_lock);
+
+ /* One more thread available. */
+ ++nready;
+ }
+ /* NOTREACHED */
+}
+
+
+static unsigned int nconns;
+
+static void
+fd_ready (int fd)
+{
+ pthread_mutex_lock (&readylist_lock);
+
+ /* Find an empty entry in FDLIST. */
+ size_t inner;
+ for (inner = 0; inner < nconns; ++inner)
+ if (fdlist[inner].next == NULL)
+ break;
+ assert (inner < nconns);
+
+ fdlist[inner].fd = fd;
+
+ if (readylist == NULL)
+ readylist = fdlist[inner].next = &fdlist[inner];
+ else
+ {
+ fdlist[inner].next = readylist->next;
+ readylist = readylist->next = &fdlist[inner];
+ }
+
+ bool do_signal = true;
+ if (__glibc_unlikely (nready == 0))
+ {
+ ++client_queued;
+ do_signal = false;
+
+ /* Try to start another thread to help out. */
+ pthread_t th;
+ if (nthreads < max_nthreads
+ && pthread_create (&th, &attr, nscd_run_worker,
+ (void *) (long int) nthreads) == 0)
+ {
+ /* We got another thread. */
+ ++nthreads;
+ /* The new thread might need a kick. */
+ do_signal = true;
+ }
+
+ }
+
+ pthread_mutex_unlock (&readylist_lock);
+
+ /* Tell one of the worker threads there is work to do. */
+ if (do_signal)
+ pthread_cond_signal (&readylist_cond);
+}
+
+
+/* Check whether restarting should happen. */
+static bool
+restart_p (time_t now)
+{
+ return (paranoia && readylist == NULL && nready == nthreads
+ && now >= restart_time);
+}
+
+
+/* Array for times a connection was accepted. */
+static time_t *starttime;
+
+#ifdef HAVE_INOTIFY
+/* Inotify event for changed file. */
+union __inev
+{
+ struct inotify_event i;
+# ifndef PATH_MAX
+# define PATH_MAX 1024
+# endif
+ char buf[sizeof (struct inotify_event) + PATH_MAX];
+};
+
+/* Returns 0 if the file is there otherwise -1. */
+int
+check_file (struct traced_file *finfo)
+{
+ struct stat64 st;
+ /* We could check mtime and if different re-add
+ the watches, and invalidate the database, but we
+ don't because we are called from inotify_check_files
+ which should be doing that work. If sufficient inotify
+ events were lost then the next pruning or invalidation
+ will do the stat and mtime check. We don't do it here to
+ keep the logic simple. */
+ if (stat64 (finfo->fname, &st) < 0)
+ return -1;
+ return 0;
+}
+
+/* Process the inotify event in INEV. If the event matches any of the files
+ registered with a database then mark that database as requiring its cache
+ to be cleared. We indicate the cache needs clearing by setting
+ TO_CLEAR[DBCNT] to true for the matching database. */
+static void
+inotify_check_files (bool *to_clear, union __inev *inev)
+{
+ /* Check which of the files changed. */
+ for (size_t dbcnt = 0; dbcnt < lastdb; ++dbcnt)
+ {
+ struct traced_file *finfo = dbs[dbcnt].traced_files;
+
+ while (finfo != NULL)
+ {
+ /* The configuration file was moved or deleted.
+ We stop watching it at that point, and reinitialize. */
+ if (finfo->inotify_descr[TRACED_FILE] == inev->i.wd
+ && ((inev->i.mask & IN_MOVE_SELF)
+ || (inev->i.mask & IN_DELETE_SELF)
+ || (inev->i.mask & IN_IGNORED)))
+ {
+ int ret;
+ bool moved = (inev->i.mask & IN_MOVE_SELF) != 0;
+
+ if (check_file (finfo) == 0)
+ {
+ dbg_log (_("ignored inotify event for `%s` (file exists)"),
+ finfo->fname);
+ return;
+ }
+
+ dbg_log (_("monitored file `%s` was %s, removing watch"),
+ finfo->fname, moved ? "moved" : "deleted");
+ /* File was moved out, remove the watch. Watches are
+ automatically removed when the file is deleted. */
+ if (moved)
+ {
+ ret = inotify_rm_watch (inotify_fd, inev->i.wd);
+ if (ret < 0)
+ dbg_log (_("failed to remove file watch `%s`: %s"),
+ finfo->fname, strerror (errno));
+ }
+ finfo->inotify_descr[TRACED_FILE] = -1;
+ to_clear[dbcnt] = true;
+ if (finfo->call_res_init)
+ res_init ();
+ return;
+ }
+ /* The configuration file was open for writing and has just closed.
+ We reset the cache and reinitialize. */
+ if (finfo->inotify_descr[TRACED_FILE] == inev->i.wd
+ && inev->i.mask & IN_CLOSE_WRITE)
+ {
+ /* Mark cache as needing to be cleared and reinitialize. */
+ dbg_log (_("monitored file `%s` was written to"), finfo->fname);
+ to_clear[dbcnt] = true;
+ if (finfo->call_res_init)
+ res_init ();
+ return;
+ }
+ /* The parent directory was moved or deleted. We trigger one last
+ invalidation. At the next pruning or invalidation we may add
+ this watch back if the file is present again. */
+ if (finfo->inotify_descr[TRACED_DIR] == inev->i.wd
+ && ((inev->i.mask & IN_DELETE_SELF)
+ || (inev->i.mask & IN_MOVE_SELF)
+ || (inev->i.mask & IN_IGNORED)))
+ {
+ bool moved = (inev->i.mask & IN_MOVE_SELF) != 0;
+ /* The directory watch may have already been removed
+ but we don't know so we just remove it again and
+ ignore the error. Then we remove the file watch.
+ Note: watches are automatically removed for deleted
+ files. */
+ if (moved)
+ inotify_rm_watch (inotify_fd, inev->i.wd);
+ if (finfo->inotify_descr[TRACED_FILE] != -1)
+ {
+ dbg_log (_("monitored parent directory `%s` was %s, removing watch on `%s`"),
+ finfo->dname, moved ? "moved" : "deleted", finfo->fname);
+ if (inotify_rm_watch (inotify_fd, finfo->inotify_descr[TRACED_FILE]) < 0)
+ dbg_log (_("failed to remove file watch `%s`: %s"),
+ finfo->dname, strerror (errno));
+ }
+ finfo->inotify_descr[TRACED_FILE] = -1;
+ finfo->inotify_descr[TRACED_DIR] = -1;
+ to_clear[dbcnt] = true;
+ if (finfo->call_res_init)
+ res_init ();
+ /* Continue to the next entry since this might be the
+ parent directory for multiple registered files and
+ we want to remove watches for all registered files. */
+ continue;
+ }
+ /* The parent directory had a create or moved to event. */
+ if (finfo->inotify_descr[TRACED_DIR] == inev->i.wd
+ && ((inev->i.mask & IN_MOVED_TO)
+ || (inev->i.mask & IN_CREATE))
+ && strcmp (inev->i.name, finfo->sfname) == 0)
+ {
+ /* We detected a directory change. We look for the creation
+ of the file we are tracking or the move of the same file
+ into the directory. */
+ int ret;
+ dbg_log (_("monitored file `%s` was %s, adding watch"),
+ finfo->fname,
+ inev->i.mask & IN_CREATE ? "created" : "moved into place");
+ /* File was moved in or created. Regenerate the watch. */
+ if (finfo->inotify_descr[TRACED_FILE] != -1)
+ inotify_rm_watch (inotify_fd,
+ finfo->inotify_descr[TRACED_FILE]);
+
+ ret = inotify_add_watch (inotify_fd,
+ finfo->fname,
+ TRACED_FILE_MASK);
+ if (ret < 0)
+ dbg_log (_("failed to add file watch `%s`: %s"),
+ finfo->fname, strerror (errno));
+
+ finfo->inotify_descr[TRACED_FILE] = ret;
+
+ /* The file is new or moved so mark cache as needing to
+ be cleared and reinitialize. */
+ to_clear[dbcnt] = true;
+ if (finfo->call_res_init)
+ res_init ();
+
+ /* Done re-adding the watch. Don't return, we may still
+ have other files in this same directory, same watch
+ descriptor, and need to process them. */
+ }
+ /* Other events are ignored, and we move on to the next file. */
+ finfo = finfo->next;
+ }
+ }
+}
+
+/* If an entry in the array of booleans TO_CLEAR is TRUE then clear the cache
+ for the associated database, otherwise do nothing. The TO_CLEAR array must
+ have LASTDB entries. */
+static inline void
+clear_db_cache (bool *to_clear)
+{
+ for (size_t dbcnt = 0; dbcnt < lastdb; ++dbcnt)
+ if (to_clear[dbcnt])
+ {
+ pthread_mutex_lock (&dbs[dbcnt].prune_lock);
+ dbs[dbcnt].clear_cache = 1;
+ pthread_mutex_unlock (&dbs[dbcnt].prune_lock);
+ pthread_cond_signal (&dbs[dbcnt].prune_cond);
+ }
+}
+
+int
+handle_inotify_events (void)
+{
+ bool to_clear[lastdb] = { false, };
+ union __inev inev;
+
+ /* Read all inotify events for files registered via
+ register_traced_file(). */
+ while (1)
+ {
+ /* Potentially read multiple events into buf. */
+ ssize_t nb = TEMP_FAILURE_RETRY (read (inotify_fd,
+ &inev.buf,
+ sizeof (inev)));
+ if (nb < (ssize_t) sizeof (struct inotify_event))
+ {
+ /* Not even 1 event. */
+ if (__glibc_unlikely (nb == -1 && errno != EAGAIN))
+ return -1;
+ /* Done reading events that are ready. */
+ break;
+ }
+ /* Process all events. The normal inotify interface delivers
+ complete events on a read and never a partial event. */
+ char *eptr = &inev.buf[0];
+ ssize_t count;
+ while (1)
+ {
+ /* Check which of the files changed. */
+ inotify_check_files (to_clear, &inev);
+ count = sizeof (struct inotify_event) + inev.i.len;
+ eptr += count;
+ nb -= count;
+ if (nb >= (ssize_t) sizeof (struct inotify_event))
+ memcpy (&inev, eptr, nb);
+ else
+ break;
+ }
+ continue;
+ }
+ /* Actually perform the cache clearing. */
+ clear_db_cache (to_clear);
+ return 0;
+}
+
+#endif
+
+static void
+__attribute__ ((__noreturn__))
+main_loop_poll (void)
+{
+ struct pollfd *conns = (struct pollfd *) xmalloc (nconns
+ * sizeof (conns[0]));
+
+ conns[0].fd = sock;
+ conns[0].events = POLLRDNORM;
+ size_t nused = 1;
+ size_t firstfree = 1;
+
+#ifdef HAVE_INOTIFY
+ if (inotify_fd != -1)
+ {
+ conns[1].fd = inotify_fd;
+ conns[1].events = POLLRDNORM;
+ nused = 2;
+ firstfree = 2;
+ }
+#endif
+
+#ifdef HAVE_NETLINK
+ size_t idx_nl_status_fd = 0;
+ if (nl_status_fd != -1)
+ {
+ idx_nl_status_fd = nused;
+ conns[nused].fd = nl_status_fd;
+ conns[nused].events = POLLRDNORM;
+ ++nused;
+ firstfree = nused;
+ }
+#endif
+
+ while (1)
+ {
+ /* Wait for any event. We wait at most a couple of seconds so
+ that we can check whether we should close any of the accepted
+ connections since we have not received a request. */
+#define MAX_ACCEPT_TIMEOUT 30
+#define MIN_ACCEPT_TIMEOUT 5
+#define MAIN_THREAD_TIMEOUT \
+ (MAX_ACCEPT_TIMEOUT * 1000 \
+ - ((MAX_ACCEPT_TIMEOUT - MIN_ACCEPT_TIMEOUT) * 1000 * nused) / (2 * nconns))
+
+ int n = poll (conns, nused, MAIN_THREAD_TIMEOUT);
+
+ time_t now = time (NULL);
+
+ /* If there is a descriptor ready for reading or there is a new
+ connection, process this now. */
+ if (n > 0)
+ {
+ if (conns[0].revents != 0)
+ {
+ /* We have a new incoming connection. Accept the connection. */
+ int fd = TEMP_FAILURE_RETRY (accept4 (sock, NULL, NULL,
+ SOCK_NONBLOCK));
+
+ /* Use the descriptor if we have not reached the limit. */
+ if (fd >= 0)
+ {
+ if (firstfree < nconns)
+ {
+ conns[firstfree].fd = fd;
+ conns[firstfree].events = POLLRDNORM;
+ starttime[firstfree] = now;
+ if (firstfree >= nused)
+ nused = firstfree + 1;
+
+ do
+ ++firstfree;
+ while (firstfree < nused && conns[firstfree].fd != -1);
+ }
+ else
+ /* We cannot use the connection so close it. */
+ close (fd);
+ }
+
+ --n;
+ }
+
+ size_t first = 1;
+#ifdef HAVE_INOTIFY
+ if (inotify_fd != -1 && conns[1].fd == inotify_fd)
+ {
+ if (conns[1].revents != 0)
+ {
+ int ret;
+ ret = handle_inotify_events ();
+ if (ret == -1)
+ {
+ /* Something went wrong when reading the inotify
+ data. Better disable inotify. */
+ dbg_log (_("disabled inotify-based monitoring after read error %d"), errno);
+ conns[1].fd = -1;
+ firstfree = 1;
+ if (nused == 2)
+ nused = 1;
+ close (inotify_fd);
+ inotify_fd = -1;
+ }
+ --n;
+ }
+
+ first = 2;
+ }
+#endif
+
+#ifdef HAVE_NETLINK
+ if (idx_nl_status_fd != 0 && conns[idx_nl_status_fd].revents != 0)
+ {
+ char buf[4096];
+ /* Read all the data. We do not interpret it here. */
+ while (TEMP_FAILURE_RETRY (read (nl_status_fd, buf,
+ sizeof (buf))) != -1)
+ ;
+
+ dbs[hstdb].head->extra_data[NSCD_HST_IDX_CONF_TIMESTAMP]
+ = __bump_nl_timestamp ();
+ }
+#endif
+
+ for (size_t cnt = first; cnt < nused && n > 0; ++cnt)
+ if (conns[cnt].revents != 0)
+ {
+ fd_ready (conns[cnt].fd);
+
+ /* Clean up the CONNS array. */
+ conns[cnt].fd = -1;
+ if (cnt < firstfree)
+ firstfree = cnt;
+ if (cnt == nused - 1)
+ do
+ --nused;
+ while (conns[nused - 1].fd == -1);
+
+ --n;
+ }
+ }
+
+ /* Now find entries which have timed out. */
+ assert (nused > 0);
+
+ /* We make the timeout length depend on the number of file
+ descriptors currently used. */
+#define ACCEPT_TIMEOUT \
+ (MAX_ACCEPT_TIMEOUT \
+ - ((MAX_ACCEPT_TIMEOUT - MIN_ACCEPT_TIMEOUT) * nused) / nconns)
+ time_t laststart = now - ACCEPT_TIMEOUT;
+
+ for (size_t cnt = nused - 1; cnt > 0; --cnt)
+ {
+ if (conns[cnt].fd != -1 && starttime[cnt] < laststart)
+ {
+ /* Remove the entry, it timed out. */
+ (void) close (conns[cnt].fd);
+ conns[cnt].fd = -1;
+
+ if (cnt < firstfree)
+ firstfree = cnt;
+ if (cnt == nused - 1)
+ do
+ --nused;
+ while (conns[nused - 1].fd == -1);
+ }
+ }
+
+ if (restart_p (now))
+ restart ();
+ }
+}
+
+
+#ifdef HAVE_EPOLL
+static void
+main_loop_epoll (int efd)
+{
+ struct epoll_event ev = { 0, };
+ int nused = 1;
+ size_t highest = 0;
+
+ /* Add the socket. */
+ ev.events = EPOLLRDNORM;
+ ev.data.fd = sock;
+ if (epoll_ctl (efd, EPOLL_CTL_ADD, sock, &ev) == -1)
+ /* We cannot use epoll. */
+ return;
+
+# ifdef HAVE_INOTIFY
+ if (inotify_fd != -1)
+ {
+ ev.events = EPOLLRDNORM;
+ ev.data.fd = inotify_fd;
+ if (epoll_ctl (efd, EPOLL_CTL_ADD, inotify_fd, &ev) == -1)
+ /* We cannot use epoll. */
+ return;
+ nused = 2;
+ }
+# endif
+
+# ifdef HAVE_NETLINK
+ if (nl_status_fd != -1)
+ {
+ ev.events = EPOLLRDNORM;
+ ev.data.fd = nl_status_fd;
+ if (epoll_ctl (efd, EPOLL_CTL_ADD, nl_status_fd, &ev) == -1)
+ /* We cannot use epoll. */
+ return;
+ }
+# endif
+
+ while (1)
+ {
+ struct epoll_event revs[100];
+# define nrevs (sizeof (revs) / sizeof (revs[0]))
+
+ int n = epoll_wait (efd, revs, nrevs, MAIN_THREAD_TIMEOUT);
+
+ time_t now = time (NULL);
+
+ for (int cnt = 0; cnt < n; ++cnt)
+ if (revs[cnt].data.fd == sock)
+ {
+ /* A new connection. */
+ int fd = TEMP_FAILURE_RETRY (accept4 (sock, NULL, NULL,
+ SOCK_NONBLOCK));
+
+ /* Use the descriptor if we have not reached the limit. */
+ if (fd >= 0)
+ {
+ /* Try to add the new descriptor. */
+ ev.data.fd = fd;
+ if (fd >= nconns
+ || epoll_ctl (efd, EPOLL_CTL_ADD, fd, &ev) == -1)
+ /* The descriptor is too large or something went
+ wrong. Close the descriptor. */
+ close (fd);
+ else
+ {
+ /* Remember when we accepted the connection. */
+ starttime[fd] = now;
+
+ if (fd > highest)
+ highest = fd;
+
+ ++nused;
+ }
+ }
+ }
+# ifdef HAVE_INOTIFY
+ else if (revs[cnt].data.fd == inotify_fd)
+ {
+ int ret;
+ ret = handle_inotify_events ();
+ if (ret == -1)
+ {
+ /* Something went wrong when reading the inotify
+ data. Better disable inotify. */
+ dbg_log (_("disabled inotify-based monitoring after read error %d"), errno);
+ (void) epoll_ctl (efd, EPOLL_CTL_DEL, inotify_fd, NULL);
+ close (inotify_fd);
+ inotify_fd = -1;
+ break;
+ }
+ }
+# endif
+# ifdef HAVE_NETLINK
+ else if (revs[cnt].data.fd == nl_status_fd)
+ {
+ char buf[4096];
+ /* Read all the data. We do not interpret it here. */
+ while (TEMP_FAILURE_RETRY (read (nl_status_fd, buf,
+ sizeof (buf))) != -1)
+ ;
+
+ __bump_nl_timestamp ();
+ }
+# endif
+ else
+ {
+ /* Remove the descriptor from the epoll descriptor. */
+ (void) epoll_ctl (efd, EPOLL_CTL_DEL, revs[cnt].data.fd, NULL);
+
+ /* Get a worker to handle the request. */
+ fd_ready (revs[cnt].data.fd);
+
+ /* Reset the time. */
+ starttime[revs[cnt].data.fd] = 0;
+ if (revs[cnt].data.fd == highest)
+ do
+ --highest;
+ while (highest > 0 && starttime[highest] == 0);
+
+ --nused;
+ }
+
+ /* Now look for descriptors for accepted connections which have
+ no reply in too long of a time. */
+ time_t laststart = now - ACCEPT_TIMEOUT;
+ assert (starttime[sock] == 0);
+# ifdef HAVE_INOTIFY
+ assert (inotify_fd == -1 || starttime[inotify_fd] == 0);
+# endif
+ assert (nl_status_fd == -1 || starttime[nl_status_fd] == 0);
+ for (int cnt = highest; cnt > STDERR_FILENO; --cnt)
+ if (starttime[cnt] != 0 && starttime[cnt] < laststart)
+ {
+ /* We are waiting for this one for too long. Close it. */
+ (void) epoll_ctl (efd, EPOLL_CTL_DEL, cnt, NULL);
+
+ (void) close (cnt);
+
+ starttime[cnt] = 0;
+ if (cnt == highest)
+ --highest;
+ }
+ else if (cnt != sock && starttime[cnt] == 0 && cnt == highest)
+ --highest;
+
+ if (restart_p (now))
+ restart ();
+ }
+}
+#endif
+
+
+/* Start all the threads we want. The initial process is thread no. 1. */
+void
+start_threads (void)
+{
+ /* Initialize the conditional variable we will use. The only
+ non-standard attribute we might use is the clock selection. */
+ pthread_condattr_t condattr;
+ pthread_condattr_init (&condattr);
+
+#if defined _POSIX_CLOCK_SELECTION && _POSIX_CLOCK_SELECTION >= 0 \
+ && defined _POSIX_MONOTONIC_CLOCK && _POSIX_MONOTONIC_CLOCK >= 0
+ /* Determine whether the monotonous clock is available. */
+ struct timespec dummy;
+# if _POSIX_MONOTONIC_CLOCK == 0
+ if (sysconf (_SC_MONOTONIC_CLOCK) > 0)
+# endif
+# if _POSIX_CLOCK_SELECTION == 0
+ if (sysconf (_SC_CLOCK_SELECTION) > 0)
+# endif
+ if (clock_getres (CLOCK_MONOTONIC, &dummy) == 0
+ && pthread_condattr_setclock (&condattr, CLOCK_MONOTONIC) == 0)
+ timeout_clock = CLOCK_MONOTONIC;
+#endif
+
+ /* Create the attribute for the threads. They are all created
+ detached. */
+ pthread_attr_init (&attr);
+ pthread_attr_setdetachstate (&attr, PTHREAD_CREATE_DETACHED);
+ /* Use 1MB stacks, twice as much for 64-bit architectures. */
+ pthread_attr_setstacksize (&attr, NSCD_THREAD_STACKSIZE);
+
+ /* We allow less than LASTDB threads only for debugging. */
+ if (debug_level == 0)
+ nthreads = MAX (nthreads, lastdb);
+
+ /* Create the threads which prune the databases. */
+ // XXX Ideally this work would be done by some of the worker threads.
+ // XXX But this is problematic since we would need to be able to wake
+ // XXX them up explicitly as well as part of the group handling the
+ // XXX ready-list. This requires an operation where we can wait on
+ // XXX two conditional variables at the same time. This operation
+ // XXX does not exist (yet).
+ for (long int i = 0; i < lastdb; ++i)
+ {
+ /* Initialize the conditional variable. */
+ if (pthread_cond_init (&dbs[i].prune_cond, &condattr) != 0)
+ {
+ dbg_log (_("could not initialize conditional variable"));
+ do_exit (1, 0, NULL);
+ }
+
+ pthread_t th;
+ if (dbs[i].enabled
+ && pthread_create (&th, &attr, nscd_run_prune, (void *) i) != 0)
+ {
+ dbg_log (_("could not start clean-up thread; terminating"));
+ do_exit (1, 0, NULL);
+ }
+ }
+
+ pthread_condattr_destroy (&condattr);
+
+ for (long int i = 0; i < nthreads; ++i)
+ {
+ pthread_t th;
+ if (pthread_create (&th, &attr, nscd_run_worker, NULL) != 0)
+ {
+ if (i == 0)
+ {
+ dbg_log (_("could not start any worker thread; terminating"));
+ do_exit (1, 0, NULL);
+ }
+
+ break;
+ }
+ }
+
+ /* Now it is safe to let the parent know that we're doing fine and it can
+ exit. */
+ notify_parent (0);
+
+ /* Determine how much room for descriptors we should initially
+ allocate. This might need to change later if we cap the number
+ with MAXCONN. */
+ const long int nfds = sysconf (_SC_OPEN_MAX);
+#define MINCONN 32
+#define MAXCONN 16384
+ if (nfds == -1 || nfds > MAXCONN)
+ nconns = MAXCONN;
+ else if (nfds < MINCONN)
+ nconns = MINCONN;
+ else
+ nconns = nfds;
+
+ /* We need memory to pass descriptors on to the worker threads. */
+ fdlist = (struct fdlist *) xcalloc (nconns, sizeof (fdlist[0]));
+ /* Array to keep track when connection was accepted. */
+ starttime = (time_t *) xcalloc (nconns, sizeof (starttime[0]));
+
+ /* In the main thread we execute the loop which handles incoming
+ connections. */
+#ifdef HAVE_EPOLL
+ int efd = epoll_create (100);
+ if (efd != -1)
+ {
+ main_loop_epoll (efd);
+ close (efd);
+ }
+#endif
+
+ main_loop_poll ();
+}
+
+
+/* Look up the uid, gid, and supplementary groups to run nscd as. When
+ this function is called, we are not listening on the nscd socket yet so
+ we can just use the ordinary lookup functions without causing a lockup */
+static void
+begin_drop_privileges (void)
+{
+ struct passwd *pwd = getpwnam (server_user);
+
+ if (pwd == NULL)
+ {
+ dbg_log (_("Failed to run nscd as user '%s'"), server_user);
+ do_exit (EXIT_FAILURE, 0,
+ _("Failed to run nscd as user '%s'"), server_user);
+ }
+
+ server_uid = pwd->pw_uid;
+ server_gid = pwd->pw_gid;
+
+ /* Save the old UID/GID if we have to change back. */
+ if (paranoia)
+ {
+ old_uid = getuid ();
+ old_gid = getgid ();
+ }
+
+ if (getgrouplist (server_user, server_gid, NULL, &server_ngroups) == 0)
+ {
+ /* This really must never happen. */
+ dbg_log (_("Failed to run nscd as user '%s'"), server_user);
+ do_exit (EXIT_FAILURE, errno,
+ _("initial getgrouplist failed"));
+ }
+
+ server_groups = (gid_t *) xmalloc (server_ngroups * sizeof (gid_t));
+
+ if (getgrouplist (server_user, server_gid, server_groups, &server_ngroups)
+ == -1)
+ {
+ dbg_log (_("Failed to run nscd as user '%s'"), server_user);
+ do_exit (EXIT_FAILURE, errno, _("getgrouplist failed"));
+ }
+}
+
+
+/* Call setgroups(), setgid(), and setuid() to drop root privileges and
+ run nscd as the user specified in the configuration file. */
+static void
+finish_drop_privileges (void)
+{
+#if defined HAVE_LIBAUDIT && defined HAVE_LIBCAP
+ /* We need to preserve the capabilities to connect to the audit daemon. */
+ cap_t new_caps = preserve_capabilities ();
+#endif
+
+ if (setgroups (server_ngroups, server_groups) == -1)
+ {
+ dbg_log (_("Failed to run nscd as user '%s'"), server_user);
+ do_exit (EXIT_FAILURE, errno, _("setgroups failed"));
+ }
+
+ int res;
+ if (paranoia)
+ res = setresgid (server_gid, server_gid, old_gid);
+ else
+ res = setgid (server_gid);
+ if (res == -1)
+ {
+ dbg_log (_("Failed to run nscd as user '%s'"), server_user);
+ do_exit (4, errno, "setgid");
+ }
+
+ if (paranoia)
+ res = setresuid (server_uid, server_uid, old_uid);
+ else
+ res = setuid (server_uid);
+ if (res == -1)
+ {
+ dbg_log (_("Failed to run nscd as user '%s'"), server_user);
+ do_exit (4, errno, "setuid");
+ }
+
+#if defined HAVE_LIBAUDIT && defined HAVE_LIBCAP
+ /* Remove the temporary capabilities. */
+ install_real_capabilities (new_caps);
+#endif
+}
diff --git a/REORG.TODO/nscd/dbg_log.c b/REORG.TODO/nscd/dbg_log.c
new file mode 100644
index 0000000000..d4b19acc0c
--- /dev/null
+++ b/REORG.TODO/nscd/dbg_log.c
@@ -0,0 +1,85 @@
+/* Copyright (c) 1998-2017 Free Software Foundation, Inc.
+ This file is part of the GNU C Library.
+ Contributed by Thorsten Kukuk <kukuk@vt.uni-paderborn.de>, 1998.
+
+ 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 <stdarg.h>
+#include <stdio.h>
+#include <string.h>
+#include <syslog.h>
+#include <unistd.h>
+#include "dbg_log.h"
+#include "nscd.h"
+
+/* if in debug mode and we have a debug file, we write the messages to it,
+ if in debug mode and no debug file, we write the messages to stderr,
+ else to syslog. */
+
+static char *logfilename;
+FILE *dbgout;
+int debug_level;
+
+void
+set_logfile (const char *logfile)
+{
+ logfilename = strdup (logfile);
+}
+
+int
+init_logfile (void)
+{
+ if (logfilename)
+ {
+ dbgout = fopen64 (logfilename, "a");
+ return dbgout == NULL ? 0 : 1;
+ }
+ return 1;
+}
+
+void
+dbg_log (const char *fmt,...)
+{
+ va_list ap;
+ char msg2[512];
+
+ va_start (ap, fmt);
+ vsnprintf (msg2, sizeof (msg2), fmt, ap);
+
+ if (debug_level > 0)
+ {
+ time_t t = time (NULL);
+
+ struct tm now;
+ localtime_r (&t, &now);
+
+ char buf[256];
+ strftime (buf, sizeof (buf), "%c", &now);
+
+ char msg[512];
+ snprintf (msg, sizeof (msg), "%s - %d: %s%s", buf, getpid (), msg2,
+ msg2[strlen (msg2) - 1] == '\n' ? "" : "\n");
+ if (dbgout)
+ {
+ fputs (msg, dbgout);
+ fflush (dbgout);
+ }
+ else
+ fputs (msg, stderr);
+ }
+ else
+ syslog (LOG_NOTICE, "%d %s", getpid (), msg2);
+
+ va_end (ap);
+}
diff --git a/REORG.TODO/nscd/dbg_log.h b/REORG.TODO/nscd/dbg_log.h
new file mode 100644
index 0000000000..158bfb39aa
--- /dev/null
+++ b/REORG.TODO/nscd/dbg_log.h
@@ -0,0 +1,30 @@
+/* Copyright (c) 1998-2017 Free Software Foundation, Inc.
+ This file is part of the GNU C Library.
+ Contributed by Thorsten Kukuk <kukuk@vt.uni-paderborn.de>, 1998.
+
+ The GNU C Library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ The GNU C Library 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
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with the GNU C Library; if not, see
+ <http://www.gnu.org/licenses/>. */
+
+#ifndef _DBG_LOG_H
+#define _DBG_LOG_H 1
+
+extern int debug_level;
+
+extern void dbg_log (const char *str, ...)
+ __attribute__ ((__format__ (__printf__, 1, 2)));;
+
+extern void set_logfile (const char *logfile);
+extern int init_logfile (void);
+
+#endif
diff --git a/REORG.TODO/nscd/gai.c b/REORG.TODO/nscd/gai.c
new file mode 100644
index 0000000000..a1aeadadc3
--- /dev/null
+++ b/REORG.TODO/nscd/gai.c
@@ -0,0 +1,48 @@
+/* Copyright (C) 2004-2017 Free Software Foundation, Inc.
+ This file is part of the GNU C Library.
+ Contributed by Ulrich Drepper <drepper@cygnus.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 <alloca.h>
+
+/* This file uses the getaddrinfo code but it compiles it without NSCD
+ support. We just need a few symbol renames. */
+#define __inet_aton inet_aton
+#define __ioctl ioctl
+#define __getsockname getsockname
+#define __socket socket
+#define __recvmsg recvmsg
+#define __bind bind
+#define __sendto sendto
+#define __strchrnul strchrnul
+#define __getline getline
+#define __qsort_r qsort_r
+/* nscd uses 1MB or 2MB thread stacks. */
+#define __libc_use_alloca(size) (size <= __MAX_ALLOCA_CUTOFF)
+
+/* We are nscd, so we don't want to be talking to ourselves. */
+#undef USE_NSCD
+
+#include <getaddrinfo.c>
+
+/* Support code. */
+#include <check_pf.c>
+#include <check_native.c>
+#ifdef HAVE_LIBIDN
+# include <libidn/idn-stub.c>
+#endif
+
+/* Some variables normally defined in libc. */
+service_user *__nss_hosts_database;
diff --git a/REORG.TODO/nscd/getgrgid_r.c b/REORG.TODO/nscd/getgrgid_r.c
new file mode 100644
index 0000000000..0e7fd21be7
--- /dev/null
+++ b/REORG.TODO/nscd/getgrgid_r.c
@@ -0,0 +1,35 @@
+/* Copyright (C) 1996-2017 Free Software Foundation, Inc.
+ This file is part of the GNU C Library.
+ Contributed by Ulrich Drepper <drepper@cygnus.com>, 1996.
+
+ 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 <grp.h>
+
+#include <grp-merge.h>
+
+#define LOOKUP_TYPE struct group
+#define FUNCTION_NAME getgrgid
+#define DATABASE_NAME group
+#define ADD_PARAMS gid_t gid
+#define ADD_VARIABLES gid
+#define BUFLEN NSS_BUFLEN_GROUP
+
+#define DEEPCOPY_FN __copy_grp
+#define MERGE_FN __merge_grp
+
+/* We are nscd, so we don't want to be talking to ourselves. */
+#undef USE_NSCD
+
+#include <nss/getXXbyYY_r.c>
diff --git a/REORG.TODO/nscd/getgrnam_r.c b/REORG.TODO/nscd/getgrnam_r.c
new file mode 100644
index 0000000000..80cb441888
--- /dev/null
+++ b/REORG.TODO/nscd/getgrnam_r.c
@@ -0,0 +1,34 @@
+/* Copyright (C) 1996-2017 Free Software Foundation, Inc.
+ This file is part of the GNU C Library.
+ Contributed by Ulrich Drepper <drepper@cygnus.com>, 1996.
+
+ 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 <grp.h>
+
+#include <grp-merge.h>
+
+#define LOOKUP_TYPE struct group
+#define FUNCTION_NAME getgrnam
+#define DATABASE_NAME group
+#define ADD_PARAMS const char *name
+#define ADD_VARIABLES name
+
+#define DEEPCOPY_FN __copy_grp
+#define MERGE_FN __merge_grp
+
+/* We are nscd, so we don't want to be talking to ourselves. */
+#undef USE_NSCD
+
+#include <nss/getXXbyYY_r.c>
diff --git a/REORG.TODO/nscd/gethstbyad_r.c b/REORG.TODO/nscd/gethstbyad_r.c
new file mode 100644
index 0000000000..b17f0d2b51
--- /dev/null
+++ b/REORG.TODO/nscd/gethstbyad_r.c
@@ -0,0 +1,46 @@
+/* Copyright (C) 1996-2017 Free Software Foundation, Inc.
+ This file is part of the GNU C Library.
+ Contributed by Ulrich Drepper <drepper@cygnus.com>, 1996.
+
+ 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 <netdb.h>
+
+
+#define LOOKUP_TYPE struct hostent
+#define FUNCTION_NAME gethostbyaddr2
+#define FUNCTION2_NAME gethostbyaddr
+#define DATABASE_NAME hosts
+#define ADD_PARAMS const void *addr, socklen_t len, int type
+#define EXTRA_PARAMS , int32_t *ttlp
+#define ADD_VARIABLES addr, len, type
+#define EXTRA_VARIABLES , ttlp
+#define NEED_H_ERRNO 1
+#define NEED__RES 1
+#define NEED__RES_HCONF 1
+
+/* We are nscd, so we don't want to be talking to ourselves. */
+#undef USE_NSCD
+
+#include "../nss/getXXbyYY_r.c"
+
+
+int
+__gethostbyaddr_r (const void *addr, socklen_t len, int type,
+ struct hostent *result_buf, char *buf, size_t buflen,
+ struct hostent **result, int *h_errnop)
+{
+ return __gethostbyaddr2_r (addr, len, type, result_buf, buf, buflen,
+ result, h_errnop, NULL);
+}
diff --git a/REORG.TODO/nscd/gethstbynm3_r.c b/REORG.TODO/nscd/gethstbynm3_r.c
new file mode 100644
index 0000000000..41bb26845d
--- /dev/null
+++ b/REORG.TODO/nscd/gethstbynm3_r.c
@@ -0,0 +1,55 @@
+/* Copyright (C) 1996-2017 Free Software Foundation, Inc.
+ This file is part of the GNU C Library.
+ Contributed by Ulrich Drepper <drepper@cygnus.com>, 1996.
+
+ 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 <ctype.h>
+#include <errno.h>
+#include <netdb.h>
+#include <string.h>
+#include <arpa/inet.h>
+#include <netinet/in.h>
+
+
+#define LOOKUP_TYPE struct hostent
+#define FUNCTION_NAME gethostbyname3
+#define FUNCTION2_NAME gethostbyname2
+#define DATABASE_NAME hosts
+#define ADD_PARAMS const char *name, int af
+#define EXTRA_PARAMS , int32_t *ttlp, char **canonp
+#define ADD_VARIABLES name, af
+#define EXTRA_VARIABLES , ttlp, canonp
+#define NEED_H_ERRNO 1
+#define NEED__RES_HCONF 1
+
+#define HANDLE_DIGITS_DOTS 1
+#define HAVE_LOOKUP_BUFFER 1
+#define HAVE_AF 1
+
+#define __inet_aton inet_aton
+
+/* We are nscd, so we don't want to be talking to ourselves. */
+#undef USE_NSCD
+
+#include "../nss/getXXbyYY_r.c"
+
+
+int
+__gethostbyname2_r (const char *name, int af, struct hostent *ret, char *buf,
+ size_t buflen, struct hostent **result, int *h_errnop)
+{
+ return __gethostbyname3_r (name, af, ret, buf, buflen, result, h_errnop,
+ NULL, NULL);
+}
diff --git a/REORG.TODO/nscd/getpwnam_r.c b/REORG.TODO/nscd/getpwnam_r.c
new file mode 100644
index 0000000000..9af95b6c01
--- /dev/null
+++ b/REORG.TODO/nscd/getpwnam_r.c
@@ -0,0 +1,31 @@
+/* Copyright (C) 1996-2017 Free Software Foundation, Inc.
+ This file is part of the GNU C Library.
+ Contributed by Ulrich Drepper <drepper@cygnus.com>, 1996.
+
+ 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 <pwd.h>
+
+
+#define LOOKUP_TYPE struct passwd
+#define FUNCTION_NAME getpwnam
+#define DATABASE_NAME passwd
+#define ADD_PARAMS const char *name
+#define ADD_VARIABLES name
+#define BUFLEN NSS_BUFLEN_PASSWD
+
+/* We are nscd, so we don't want to be talking to ourselves. */
+#undef USE_NSCD
+
+#include <nss/getXXbyYY_r.c>
diff --git a/REORG.TODO/nscd/getpwuid_r.c b/REORG.TODO/nscd/getpwuid_r.c
new file mode 100644
index 0000000000..fae2a141be
--- /dev/null
+++ b/REORG.TODO/nscd/getpwuid_r.c
@@ -0,0 +1,31 @@
+/* Copyright (C) 1996-2017 Free Software Foundation, Inc.
+ This file is part of the GNU C Library.
+ Contributed by Ulrich Drepper <drepper@cygnus.com>, 1996.
+
+ 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 <pwd.h>
+
+
+#define LOOKUP_TYPE struct passwd
+#define FUNCTION_NAME getpwuid
+#define DATABASE_NAME passwd
+#define ADD_PARAMS uid_t uid
+#define ADD_VARIABLES uid
+#define BUFLEN NSS_BUFLEN_PASSWD
+
+/* We are nscd, so we don't want to be talking to ourselves. */
+#undef USE_NSCD
+
+#include <nss/getXXbyYY_r.c>
diff --git a/REORG.TODO/nscd/getsrvbynm_r.c b/REORG.TODO/nscd/getsrvbynm_r.c
new file mode 100644
index 0000000000..ef6eac0358
--- /dev/null
+++ b/REORG.TODO/nscd/getsrvbynm_r.c
@@ -0,0 +1,30 @@
+/* Copyright (C) 1996-2017 Free Software Foundation, Inc.
+ This file is part of the GNU C Library.
+ Contributed by Ulrich Drepper <drepper@cygnus.com>, 1996.
+
+ 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 <netdb.h>
+
+
+#define LOOKUP_TYPE struct servent
+#define FUNCTION_NAME getservbyname
+#define DATABASE_NAME services
+#define ADD_PARAMS const char *name, const char *proto
+#define ADD_VARIABLES name, proto
+
+/* We are nscd, so we don't want to be talking to ourselves. */
+#undef USE_NSCD
+
+#include "../nss/getXXbyYY_r.c"
diff --git a/REORG.TODO/nscd/getsrvbypt_r.c b/REORG.TODO/nscd/getsrvbypt_r.c
new file mode 100644
index 0000000000..10a3d57ab4
--- /dev/null
+++ b/REORG.TODO/nscd/getsrvbypt_r.c
@@ -0,0 +1,30 @@
+/* Copyright (C) 1996-2017 Free Software Foundation, Inc.
+ This file is part of the GNU C Library.
+ Contributed by Ulrich Drepper <drepper@cygnus.com>, 1996.
+
+ 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 <netdb.h>
+
+
+#define LOOKUP_TYPE struct servent
+#define FUNCTION_NAME getservbyport
+#define DATABASE_NAME services
+#define ADD_PARAMS int port, const char *proto
+#define ADD_VARIABLES port, proto
+
+/* We are nscd, so we don't want to be talking to ourselves. */
+#undef USE_NSCD
+
+#include "../nss/getXXbyYY_r.c"
diff --git a/REORG.TODO/nscd/grpcache.c b/REORG.TODO/nscd/grpcache.c
new file mode 100644
index 0000000000..d2ad53509d
--- /dev/null
+++ b/REORG.TODO/nscd/grpcache.c
@@ -0,0 +1,572 @@
+/* Cache handling for group lookup.
+ Copyright (C) 1998-2017 Free Software Foundation, Inc.
+ This file is part of the GNU C Library.
+ Contributed by Ulrich Drepper <drepper@cygnus.com>, 1998.
+
+ 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 <error.h>
+#include <grp.h>
+#include <libintl.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/mman.h>
+#include <sys/socket.h>
+#include <stackinfo.h>
+
+#include "nscd.h"
+#include "dbg_log.h"
+#ifdef HAVE_SENDFILE
+# include <kernel-features.h>
+#endif
+
+/* This is the standard reply in case the service is disabled. */
+static const gr_response_header disabled =
+{
+ .version = NSCD_VERSION,
+ .found = -1,
+ .gr_name_len = 0,
+ .gr_passwd_len = 0,
+ .gr_gid = -1,
+ .gr_mem_cnt = 0,
+};
+
+/* This is the struct describing how to write this record. */
+const struct iovec grp_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 gr_response_header notfound =
+{
+ .version = NSCD_VERSION,
+ .found = 0,
+ .gr_name_len = 0,
+ .gr_passwd_len = 0,
+ .gr_gid = -1,
+ .gr_mem_cnt = 0,
+};
+
+
+static time_t
+cache_addgr (struct database_dyn *db, int fd, request_header *req,
+ const void *key, struct group *grp, uid_t owner,
+ struct hashentry *const he, struct datahead *dh, int errval)
+{
+ bool all_written = true;
+ ssize_t total;
+ time_t t = time (NULL);
+
+ /* We allocate all data in one memory block: the iov vector,
+ the response header and the dataset itself. */
+ struct dataset
+ {
+ struct datahead head;
+ gr_response_header resp;
+ char strdata[0];
+ } *dataset;
+
+ assert (offsetof (struct dataset, resp) == offsetof (struct datahead, data));
+
+ time_t timeout = MAX_TIMEOUT_VALUE;
+ if (grp == NULL)
+ {
+ if (he != NULL && errval == 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)
+ /* 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 = t + db->postimeout;
+
+ total = 0;
+ }
+ 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, &notfound, total,
+ MSG_NOSIGNAL)) != total)
+ all_written = false;
+
+ /* If we have a transient error or cannot permanently store
+ the result, so be it. */
+ if (errno == EAGAIN || __builtin_expect (db->negtimeout == 0, 0))
+ {
+ /* Mark the old entry as obsolete. */
+ if (dh != NULL)
+ dh->usable = false;
+ }
+ 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, &notfound, total);
+
+ /* Copy the key data. */
+ memcpy (dataset->strdata, key, req->key_len);
+
+ /* 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 (struct dataset) + req->key_len, MS_ASYNC);
+ }
+
+ (void) cache_add (req->type, &dataset->strdata, req->key_len,
+ &dataset->head, true, db, owner, he == NULL);
+
+ pthread_rwlock_unlock (&db->lock);
+
+ /* Mark the old entry as obsolete. */
+ if (dh != NULL)
+ dh->usable = false;
+ }
+ }
+ }
+ else
+ {
+ /* Determine the I/O structure. */
+ size_t gr_name_len = strlen (grp->gr_name) + 1;
+ size_t gr_passwd_len = strlen (grp->gr_passwd) + 1;
+ size_t gr_mem_cnt = 0;
+ uint32_t *gr_mem_len;
+ size_t gr_mem_len_total = 0;
+ char *gr_name;
+ char *cp;
+ const size_t key_len = strlen (key);
+ const size_t buf_len = 3 * sizeof (grp->gr_gid) + key_len + 1;
+ size_t alloca_used = 0;
+ char *buf = alloca_account (buf_len, alloca_used);
+ ssize_t n;
+ size_t cnt;
+
+ /* We need this to insert the `bygid' entry. */
+ int key_offset;
+ n = snprintf (buf, buf_len, "%d%c%n%s", grp->gr_gid, '\0',
+ &key_offset, (char *) key) + 1;
+
+ /* Determine the length of all members. */
+ while (grp->gr_mem[gr_mem_cnt])
+ ++gr_mem_cnt;
+ gr_mem_len = alloca_account (gr_mem_cnt * sizeof (uint32_t), alloca_used);
+ for (gr_mem_cnt = 0; grp->gr_mem[gr_mem_cnt]; ++gr_mem_cnt)
+ {
+ gr_mem_len[gr_mem_cnt] = strlen (grp->gr_mem[gr_mem_cnt]) + 1;
+ gr_mem_len_total += gr_mem_len[gr_mem_cnt];
+ }
+
+ total = (offsetof (struct dataset, strdata)
+ + gr_mem_cnt * sizeof (uint32_t)
+ + gr_name_len + gr_passwd_len + gr_mem_len_total);
+
+ /* If we refill the cache, first assume the reconrd did not
+ change. Allocate memory on the cache since it is likely
+ discarded anyway. If it turns out to be necessary to have a
+ new record we can still allocate real memory. */
+ bool dataset_temporary = false;
+ bool dataset_malloced = false;
+ dataset = NULL;
+
+ if (he == NULL)
+ {
+ /* Prevent an INVALIDATE request from pruning the data between
+ the two calls to cache_add. */
+ if (db->propagate)
+ pthread_mutex_lock (&db->prune_run_lock);
+ dataset = (struct dataset *) mempool_alloc (db, total + n, 1);
+ }
+
+ if (dataset == NULL)
+ {
+ if (he == NULL && db->propagate)
+ pthread_mutex_unlock (&db->prune_run_lock);
+
+ /* We cannot permanently add the result in the moment. But
+ we can provide the result as is. Store the data in some
+ temporary memory. */
+ if (! __libc_use_alloca (alloca_used + total + n))
+ {
+ dataset = malloc (total + n);
+ /* Perhaps we should log a message that we were unable
+ to allocate memory for a large request. */
+ if (dataset == NULL)
+ goto out;
+ dataset_malloced = true;
+ }
+ else
+ dataset = alloca_account (total + n, alloca_used);
+
+ /* We cannot add this record to the permanent database. */
+ dataset_temporary = true;
+ }
+
+ timeout = datahead_init_pos (&dataset->head, total + n,
+ total - offsetof (struct dataset, resp),
+ he == NULL ? 0 : dh->nreloads + 1,
+ db->postimeout);
+
+ dataset->resp.version = NSCD_VERSION;
+ dataset->resp.found = 1;
+ dataset->resp.gr_name_len = gr_name_len;
+ dataset->resp.gr_passwd_len = gr_passwd_len;
+ dataset->resp.gr_gid = grp->gr_gid;
+ dataset->resp.gr_mem_cnt = gr_mem_cnt;
+
+ cp = dataset->strdata;
+
+ /* This is the member string length array. */
+ cp = mempcpy (cp, gr_mem_len, gr_mem_cnt * sizeof (uint32_t));
+ gr_name = cp;
+ cp = mempcpy (cp, grp->gr_name, gr_name_len);
+ cp = mempcpy (cp, grp->gr_passwd, gr_passwd_len);
+
+ for (cnt = 0; cnt < gr_mem_cnt; ++cnt)
+ cp = mempcpy (cp, grp->gr_mem[cnt], gr_mem_len[cnt]);
+
+ /* Finally the stringified GID value. */
+ memcpy (cp, buf, n);
+ char *key_copy = cp + key_offset;
+ assert (key_copy == (char *) rawmemchr (cp, '\0') + 1);
+
+ assert (cp == dataset->strdata + total - offsetof (struct dataset,
+ strdata));
+
+ /* Now we can determine whether on refill we have to create a new
+ record or not. */
+ if (he != NULL)
+ {
+ assert (fd == -1);
+
+ if (total + n == 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->nreloads;
+
+ /* If the new record was allocated via malloc, then we must free
+ it here. */
+ if (dataset_malloced)
+ free (dataset);
+ }
+ else
+ {
+ /* We have to create a new record. Just allocate
+ appropriate memory and copy it. */
+ struct dataset *newp
+ = (struct dataset *) mempool_alloc (db, total + n, 1);
+ if (newp != NULL)
+ {
+ /* Adjust pointers into the memory block. */
+ gr_name = (char *) newp + (gr_name - (char *) dataset);
+ cp = (char *) newp + (cp - (char *) dataset);
+ key_copy = (char *) newp + (key_copy - (char *) dataset);
+
+ dataset = memcpy (newp, dataset, total + n);
+ dataset_temporary = 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) && ! dataset_temporary)
+ {
+ 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));
+ ssize_t written = sendfileall (fd, db->wr_fd,
+ (char *) &dataset->resp
+ - (char *) db->head,
+ dataset->head.recsize);
+ if (written != dataset->head.recsize)
+ {
+# ifndef __ASSUME_SENDFILE
+ if (written == -1 && errno == ENOSYS)
+ goto use_write;
+# endif
+ all_written = false;
+ }
+ }
+ else
+# ifndef __ASSUME_SENDFILE
+ use_write:
+# endif
+#endif
+ if (writeall (fd, &dataset->resp, dataset->head.recsize)
+ != dataset->head.recsize)
+ all_written = false;
+ }
+
+ /* Add the record to the database. But only if it has not been
+ stored on the stack. */
+ if (! dataset_temporary)
+ {
+ /* 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 + n,
+ MS_ASYNC);
+ }
+
+ /* NB: in the following code we always must add the entry
+ marked with FIRST first. Otherwise we end up with
+ dangling "pointers" in case a latter hash entry cannot be
+ added. */
+ bool first = true;
+
+ /* If the request was by GID, add that entry first. */
+ if (req->type == GETGRBYGID)
+ {
+ if (cache_add (GETGRBYGID, cp, key_offset, &dataset->head, true,
+ db, owner, he == NULL) < 0)
+ goto out;
+
+ first = false;
+ }
+ /* If the key is different from the name add a separate entry. */
+ else if (strcmp (key_copy, gr_name) != 0)
+ {
+ if (cache_add (GETGRBYNAME, key_copy, key_len + 1,
+ &dataset->head, true, db, owner, he == NULL) < 0)
+ goto out;
+
+ first = false;
+ }
+
+ /* We have to add the value for both, byname and byuid. */
+ if ((req->type == GETGRBYNAME || db->propagate)
+ && __builtin_expect (cache_add (GETGRBYNAME, gr_name,
+ gr_name_len,
+ &dataset->head, first, db, owner,
+ he == NULL)
+ == 0, 1))
+ {
+ if (req->type == GETGRBYNAME && db->propagate)
+ (void) cache_add (GETGRBYGID, cp, key_offset, &dataset->head,
+ false, db, owner, false);
+ }
+
+ out:
+ pthread_rwlock_unlock (&db->lock);
+ if (he == NULL && db->propagate)
+ pthread_mutex_unlock (&db->prune_run_lock);
+ }
+ }
+
+ if (__builtin_expect (!all_written, 0) && debug_level > 0)
+ {
+ char buf[256];
+ dbg_log (_("short write in %s: %s"), __FUNCTION__,
+ strerror_r (errno, buf, sizeof (buf)));
+ }
+
+ return timeout;
+}
+
+
+union keytype
+{
+ void *v;
+ gid_t g;
+};
+
+
+static int
+lookup (int type, union keytype key, struct group *resultbufp, char *buffer,
+ size_t buflen, struct group **grp)
+{
+ if (type == GETGRBYNAME)
+ return __getgrnam_r (key.v, resultbufp, buffer, buflen, grp);
+ else
+ return __getgrgid_r (key.g, resultbufp, buffer, buflen, grp);
+}
+
+
+static time_t
+addgrbyX (struct database_dyn *db, int fd, request_header *req,
+ union keytype key, const char *keystr, uid_t uid,
+ struct hashentry *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. */
+ size_t buflen = 1024;
+ char *buffer = (char *) alloca (buflen);
+ struct group resultbuf;
+ struct group *grp;
+ bool use_malloc = false;
+ int errval = 0;
+
+ if (__glibc_unlikely (debug_level > 0))
+ {
+ if (he == NULL)
+ dbg_log (_("Haven't found \"%s\" in group cache!"), keystr);
+ else
+ dbg_log (_("Reloading \"%s\" in group cache!"), keystr);
+ }
+
+ while (lookup (req->type, key, &resultbuf, buffer, buflen, &grp) != 0
+ && (errval = errno) == ERANGE)
+ {
+ errno = 0;
+
+ if (__glibc_unlikely (buflen > 32768))
+ {
+ char *old_buffer = buffer;
+ buflen *= 2;
+ buffer = (char *) realloc (use_malloc ? buffer : NULL, buflen);
+ if (buffer == NULL)
+ {
+ /* We ran out of memory. We cannot do anything but
+ sending a negative response. In reality this should
+ never happen. */
+ grp = NULL;
+ buffer = old_buffer;
+
+ /* We set the error to indicate this is (possibly) a
+ temporary error and that it does not mean the entry
+ is not available at all. */
+ errval = EAGAIN;
+ break;
+ }
+ use_malloc = true;
+ }
+ else
+ /* Allocate a new buffer on the stack. If possible combine it
+ with the previously allocated buffer. */
+ buffer = (char *) extend_alloca (buffer, buflen, 2 * buflen);
+ }
+
+ time_t timeout = cache_addgr (db, fd, req, keystr, grp, uid, he, dh, errval);
+
+ if (use_malloc)
+ free (buffer);
+
+ return timeout;
+}
+
+
+void
+addgrbyname (struct database_dyn *db, int fd, request_header *req,
+ void *key, uid_t uid)
+{
+ union keytype u = { .v = key };
+
+ addgrbyX (db, fd, req, u, key, uid, NULL, NULL);
+}
+
+
+time_t
+readdgrbyname (struct database_dyn *db, struct hashentry *he,
+ struct datahead *dh)
+{
+ request_header req =
+ {
+ .type = GETGRBYNAME,
+ .key_len = he->len
+ };
+ union keytype u = { .v = db->data + he->key };
+
+ return addgrbyX (db, -1, &req, u, db->data + he->key, he->owner, he, dh);
+}
+
+
+void
+addgrbygid (struct database_dyn *db, int fd, request_header *req,
+ void *key, uid_t uid)
+{
+ char *ep;
+ gid_t gid = strtoul ((char *) key, &ep, 10);
+
+ if (*(char *) key == '\0' || *ep != '\0') /* invalid numeric uid */
+ {
+ if (debug_level > 0)
+ dbg_log (_("Invalid numeric gid \"%s\"!"), (char *) key);
+
+ errno = EINVAL;
+ return;
+ }
+
+ union keytype u = { .g = gid };
+
+ addgrbyX (db, fd, req, u, key, uid, NULL, NULL);
+}
+
+
+time_t
+readdgrbygid (struct database_dyn *db, struct hashentry *he,
+ struct datahead *dh)
+{
+ char *ep;
+ gid_t gid = strtoul (db->data + he->key, &ep, 10);
+
+ /* Since the key has been added before it must be OK. */
+ assert (*(db->data + he->key) != '\0' && *ep == '\0');
+
+ request_header req =
+ {
+ .type = GETGRBYGID,
+ .key_len = he->len
+ };
+ union keytype u = { .g = gid };
+
+ return addgrbyX (db, -1, &req, u, db->data + he->key, he->owner, he, dh);
+}
diff --git a/REORG.TODO/nscd/hstcache.c b/REORG.TODO/nscd/hstcache.c
new file mode 100644
index 0000000000..9f6ce979ac
--- /dev/null
+++ b/REORG.TODO/nscd/hstcache.c
@@ -0,0 +1,619 @@
+/* Cache handling for host lookup.
+ Copyright (C) 1998-2017 Free Software Foundation, Inc.
+ This file is part of the GNU C Library.
+ Contributed by Ulrich Drepper <drepper@cygnus.com>, 1998.
+
+ 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 <error.h>
+#include <libintl.h>
+#include <netdb.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+#include <stdint.h>
+#include <arpa/inet.h>
+#include <arpa/nameser.h>
+#include <sys/mman.h>
+#include <stackinfo.h>
+
+#include "nscd.h"
+#include "dbg_log.h"
+#ifdef HAVE_SENDFILE
+# include <kernel-features.h>
+#endif
+
+
+/* This is the standard reply in case the service is disabled. */
+static const hst_response_header disabled =
+{
+ .version = NSCD_VERSION,
+ .found = -1,
+ .h_name_len = 0,
+ .h_aliases_cnt = 0,
+ .h_addrtype = -1,
+ .h_length = -1,
+ .h_addr_list_cnt = 0,
+ .error = NETDB_INTERNAL
+};
+
+/* This is the struct describing how to write this record. */
+const struct iovec hst_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 hst_response_header notfound =
+{
+ .version = NSCD_VERSION,
+ .found = 0,
+ .h_name_len = 0,
+ .h_aliases_cnt = 0,
+ .h_addrtype = -1,
+ .h_length = -1,
+ .h_addr_list_cnt = 0,
+ .error = HOST_NOT_FOUND
+};
+
+
+/* This is the standard reply in case there are temporary problems. */
+static const hst_response_header tryagain =
+{
+ .version = NSCD_VERSION,
+ .found = 0,
+ .h_name_len = 0,
+ .h_aliases_cnt = 0,
+ .h_addrtype = -1,
+ .h_length = -1,
+ .h_addr_list_cnt = 0,
+ .error = TRY_AGAIN
+};
+
+
+static time_t
+cache_addhst (struct database_dyn *db, int fd, request_header *req,
+ const void *key, struct hostent *hst, uid_t owner,
+ struct hashentry *const he, struct datahead *dh, int errval,
+ int32_t ttl)
+{
+ bool all_written = true;
+ time_t t = time (NULL);
+
+ /* We allocate all data in one memory block: the iov vector,
+ the response header and the dataset itself. */
+ struct dataset
+ {
+ struct datahead head;
+ hst_response_header resp;
+ char strdata[0];
+ } *dataset;
+
+ assert (offsetof (struct dataset, resp) == offsetof (struct datahead, data));
+
+ time_t timeout = MAX_TIMEOUT_VALUE;
+ if (hst == NULL)
+ {
+ if (he != NULL && errval == 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)
+ /* 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 = t + dh->ttl;
+ }
+ else
+ {
+ /* We have no data. This means we send the standard reply for this
+ case. Possibly this is only temporary. */
+ ssize_t total = sizeof (notfound);
+ assert (sizeof (notfound) == sizeof (tryagain));
+
+ const hst_response_header *resp = (errval == EAGAIN
+ ? &tryagain : &notfound);
+
+ if (fd != -1 &&
+ TEMP_FAILURE_RETRY (send (fd, resp, total,
+ MSG_NOSIGNAL)) != total)
+ all_written = false;
+
+ /* If we have a transient error or cannot permanently store
+ the result, so be it. */
+ if (errval == EAGAIN || __builtin_expect (db->negtimeout == 0, 0))
+ {
+ /* Mark the old entry as obsolete. */
+ if (dh != NULL)
+ dh->usable = false;
+ }
+ 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,
+ (ttl == INT32_MAX
+ ? db->negtimeout : ttl));
+
+ /* This is the reply. */
+ memcpy (&dataset->resp, resp, total);
+
+ /* Copy the key data. */
+ memcpy (dataset->strdata, key, req->key_len);
+
+ /* 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 (struct dataset) + req->key_len, MS_ASYNC);
+ }
+
+ (void) cache_add (req->type, &dataset->strdata, req->key_len,
+ &dataset->head, true, db, owner, he == NULL);
+
+ pthread_rwlock_unlock (&db->lock);
+
+ /* Mark the old entry as obsolete. */
+ if (dh != NULL)
+ dh->usable = false;
+ }
+ }
+ }
+ else
+ {
+ /* Determine the I/O structure. */
+ size_t h_name_len = strlen (hst->h_name) + 1;
+ size_t h_aliases_cnt;
+ uint32_t *h_aliases_len;
+ size_t h_addr_list_cnt;
+ char *addresses;
+ char *aliases;
+ char *key_copy = NULL;
+ char *cp;
+ size_t cnt;
+ ssize_t total;
+
+ /* Determine the number of aliases. */
+ h_aliases_cnt = 0;
+ for (cnt = 0; hst->h_aliases[cnt] != NULL; ++cnt)
+ ++h_aliases_cnt;
+ /* Determine the length of all aliases. */
+ h_aliases_len = (uint32_t *) alloca (h_aliases_cnt * sizeof (uint32_t));
+ total = 0;
+ for (cnt = 0; cnt < h_aliases_cnt; ++cnt)
+ {
+ h_aliases_len[cnt] = strlen (hst->h_aliases[cnt]) + 1;
+ total += h_aliases_len[cnt];
+ }
+
+ /* Determine the number of addresses. */
+ h_addr_list_cnt = 0;
+ while (hst->h_addr_list[h_addr_list_cnt] != NULL)
+ ++h_addr_list_cnt;
+
+ if (h_addr_list_cnt == 0)
+ /* Invalid entry. */
+ return MAX_TIMEOUT_VALUE;
+
+ total += (sizeof (struct dataset)
+ + h_name_len
+ + h_aliases_cnt * sizeof (uint32_t)
+ + h_addr_list_cnt * hst->h_length);
+
+ /* If we refill the cache, first assume the reconrd did not
+ change. Allocate memory on the cache since it is likely
+ discarded anyway. If it turns out to be necessary to have a
+ new record we can still allocate real memory. */
+ bool alloca_used = false;
+ dataset = NULL;
+
+ /* If the record contains more than one IP address (used for
+ load balancing etc) don't cache the entry. This is something
+ the current cache handling cannot handle and it is more than
+ questionable whether it is worthwhile complicating the cache
+ handling just for handling such a special case. */
+ if (he == NULL && h_addr_list_cnt == 1)
+ 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;
+ }
+
+ 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);
+
+ dataset->resp.version = NSCD_VERSION;
+ dataset->resp.found = 1;
+ dataset->resp.h_name_len = h_name_len;
+ dataset->resp.h_aliases_cnt = h_aliases_cnt;
+ dataset->resp.h_addrtype = hst->h_addrtype;
+ dataset->resp.h_length = hst->h_length;
+ dataset->resp.h_addr_list_cnt = h_addr_list_cnt;
+ dataset->resp.error = NETDB_SUCCESS;
+
+ /* Make sure there is no gap. */
+ assert ((char *) (&dataset->resp.error + 1) == dataset->strdata);
+
+ cp = dataset->strdata;
+
+ cp = mempcpy (cp, hst->h_name, h_name_len);
+ cp = mempcpy (cp, h_aliases_len, h_aliases_cnt * sizeof (uint32_t));
+
+ /* The normal addresses first. */
+ addresses = cp;
+ for (cnt = 0; cnt < h_addr_list_cnt; ++cnt)
+ cp = mempcpy (cp, hst->h_addr_list[cnt], hst->h_length);
+
+ /* Then the aliases. */
+ aliases = cp;
+ for (cnt = 0; cnt < h_aliases_cnt; ++cnt)
+ cp = mempcpy (cp, hst->h_aliases[cnt], h_aliases_len[cnt]);
+
+ assert (cp
+ == dataset->strdata + total - offsetof (struct dataset,
+ strdata));
+
+ /* If we are adding a GETHOSTBYNAME{,v6} entry we must be prepared
+ that the answer we get from the NSS does not contain the key
+ itself. This is the case if the resolver is used and the name
+ is extended by the domainnames from /etc/resolv.conf. Therefore
+ we explicitly add the name here. */
+ key_copy = memcpy (cp, key, req->key_len);
+
+ assert ((char *) &dataset->resp + dataset->head.recsize == cp);
+
+ /* 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. */
+ assert (h_addr_list_cnt == 1);
+ dh->ttl = dataset->head.ttl;
+ dh->timeout = dataset->head.timeout;
+ ++dh->nreloads;
+ }
+ else
+ {
+ if (h_addr_list_cnt == 1)
+ {
+ /* 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 (newp != NULL)
+ {
+ /* Adjust pointers into the memory block. */
+ addresses = (char *) newp + (addresses
+ - (char *) dataset);
+ aliases = (char *) newp + (aliases - (char *) dataset);
+ assert (key_copy != NULL);
+ 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 keep the receiver waiting. */
+ 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));
+ ssize_t written = sendfileall (fd, db->wr_fd,
+ (char *) &dataset->resp
+ - (char *) db->head,
+ dataset->head.recsize);
+ if (written != dataset->head.recsize)
+ {
+# ifndef __ASSUME_SENDFILE
+ if (written == -1 && errno == ENOSYS)
+ goto use_write;
+# endif
+ all_written = false;
+ }
+ }
+ else
+# ifndef __ASSUME_SENDFILE
+ use_write:
+# endif
+#endif
+ if (writeall (fd, &dataset->resp, dataset->head.recsize)
+ != dataset->head.recsize)
+ all_written = false;
+ }
+
+ /* Add the record to the database. But only if it has not been
+ stored on the stack.
+
+ If the record contains more than one IP address (used for
+ load balancing etc) don't cache the entry. This is something
+ the current cache handling cannot handle and it is more than
+ questionable whether it is worthwhile complicating the cache
+ handling just for handling such a special case. */
+ if (! 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);
+ }
+
+ /* NB: the following code is really complicated. It has
+ seemlingly duplicated code paths which do the same. The
+ problem is that we always must add the hash table entry
+ with the FIRST flag set first. Otherwise we get dangling
+ pointers in case memory allocation fails. */
+ assert (hst->h_addr_list[1] == NULL);
+
+ /* Avoid adding names if more than one address is available. See
+ above for more info. */
+ assert (req->type == GETHOSTBYNAME
+ || req->type == GETHOSTBYNAMEv6
+ || req->type == GETHOSTBYADDR
+ || req->type == GETHOSTBYADDRv6);
+
+ (void) cache_add (req->type, key_copy, req->key_len,
+ &dataset->head, true, db, owner, he == NULL);
+
+ pthread_rwlock_unlock (&db->lock);
+ }
+ }
+
+ if (__builtin_expect (!all_written, 0) && debug_level > 0)
+ {
+ char buf[256];
+ dbg_log (_("short write in %s: %s"), __FUNCTION__,
+ strerror_r (errno, buf, sizeof (buf)));
+ }
+
+ return timeout;
+}
+
+
+static int
+lookup (int type, void *key, struct hostent *resultbufp, char *buffer,
+ size_t buflen, struct hostent **hst, int32_t *ttlp)
+{
+ if (type == GETHOSTBYNAME)
+ return __gethostbyname3_r (key, AF_INET, resultbufp, buffer, buflen, hst,
+ &h_errno, ttlp, NULL);
+ if (type == GETHOSTBYNAMEv6)
+ return __gethostbyname3_r (key, AF_INET6, resultbufp, buffer, buflen, hst,
+ &h_errno, ttlp, NULL);
+ if (type == GETHOSTBYADDR)
+ return __gethostbyaddr2_r (key, NS_INADDRSZ, AF_INET, resultbufp, buffer,
+ buflen, hst, &h_errno, ttlp);
+ return __gethostbyaddr2_r (key, NS_IN6ADDRSZ, AF_INET6, resultbufp, buffer,
+ buflen, hst, &h_errno, ttlp);
+}
+
+
+static time_t
+addhstbyX (struct database_dyn *db, int fd, request_header *req,
+ void *key, uid_t uid, struct hashentry *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. */
+ int buflen = 1024;
+ char *buffer = (char *) alloca (buflen);
+ struct hostent resultbuf;
+ struct hostent *hst;
+ bool use_malloc = false;
+ int errval = 0;
+ int32_t ttl = INT32_MAX;
+
+ if (__glibc_unlikely (debug_level > 0))
+ {
+ const char *str;
+ char buf[INET6_ADDRSTRLEN + 1];
+ if (req->type == GETHOSTBYNAME || req->type == GETHOSTBYNAMEv6)
+ str = key;
+ else
+ str = inet_ntop (req->type == GETHOSTBYADDR ? AF_INET : AF_INET6,
+ key, buf, sizeof (buf));
+
+ if (he == NULL)
+ dbg_log (_("Haven't found \"%s\" in hosts cache!"), (char *) str);
+ else
+ dbg_log (_("Reloading \"%s\" in hosts cache!"), (char *) str);
+ }
+
+ while (lookup (req->type, key, &resultbuf, buffer, buflen, &hst, &ttl) != 0
+ && h_errno == NETDB_INTERNAL
+ && (errval = errno) == ERANGE)
+ {
+ errno = 0;
+
+ if (__glibc_unlikely (buflen > 32768))
+ {
+ char *old_buffer = buffer;
+ buflen *= 2;
+ buffer = (char *) realloc (use_malloc ? buffer : NULL, buflen);
+ if (buffer == NULL)
+ {
+ /* We ran out of memory. We cannot do anything but
+ sending a negative response. In reality this should
+ never happen. */
+ hst = NULL;
+ buffer = old_buffer;
+
+ /* We set the error to indicate this is (possibly) a
+ temporary error and that it does not mean the entry
+ is not available at all. */
+ h_errno = TRY_AGAIN;
+ errval = EAGAIN;
+ break;
+ }
+ use_malloc = true;
+ }
+ else
+ /* Allocate a new buffer on the stack. If possible combine it
+ with the previously allocated buffer. */
+ buffer = (char *) extend_alloca (buffer, buflen, 2 * buflen);
+ }
+
+ time_t timeout = cache_addhst (db, fd, req, key, hst, uid, he, dh,
+ h_errno == TRY_AGAIN ? errval : 0, ttl);
+
+ if (use_malloc)
+ free (buffer);
+
+ return timeout;
+}
+
+
+void
+addhstbyname (struct database_dyn *db, int fd, request_header *req,
+ void *key, uid_t uid)
+{
+ addhstbyX (db, fd, req, key, uid, NULL, NULL);
+}
+
+
+time_t
+readdhstbyname (struct database_dyn *db, struct hashentry *he,
+ struct datahead *dh)
+{
+ request_header req =
+ {
+ .type = GETHOSTBYNAME,
+ .key_len = he->len
+ };
+
+ return addhstbyX (db, -1, &req, db->data + he->key, he->owner, he, dh);
+}
+
+
+void
+addhstbyaddr (struct database_dyn *db, int fd, request_header *req,
+ void *key, uid_t uid)
+{
+ addhstbyX (db, fd, req, key, uid, NULL, NULL);
+}
+
+
+time_t
+readdhstbyaddr (struct database_dyn *db, struct hashentry *he,
+ struct datahead *dh)
+{
+ request_header req =
+ {
+ .type = GETHOSTBYADDR,
+ .key_len = he->len
+ };
+
+ return addhstbyX (db, -1, &req, db->data + he->key, he->owner, he, dh);
+}
+
+
+void
+addhstbynamev6 (struct database_dyn *db, int fd, request_header *req,
+ void *key, uid_t uid)
+{
+ addhstbyX (db, fd, req, key, uid, NULL, NULL);
+}
+
+
+time_t
+readdhstbynamev6 (struct database_dyn *db, struct hashentry *he,
+ struct datahead *dh)
+{
+ request_header req =
+ {
+ .type = GETHOSTBYNAMEv6,
+ .key_len = he->len
+ };
+
+ return addhstbyX (db, -1, &req, db->data + he->key, he->owner, he, dh);
+}
+
+
+void
+addhstbyaddrv6 (struct database_dyn *db, int fd, request_header *req,
+ void *key, uid_t uid)
+{
+ addhstbyX (db, fd, req, key, uid, NULL, NULL);
+}
+
+
+time_t
+readdhstbyaddrv6 (struct database_dyn *db, struct hashentry *he,
+ struct datahead *dh)
+{
+ request_header req =
+ {
+ .type = GETHOSTBYADDRv6,
+ .key_len = he->len
+ };
+
+ return addhstbyX (db, -1, &req, db->data + he->key, he->owner, he, dh);
+}
diff --git a/REORG.TODO/nscd/initgrcache.c b/REORG.TODO/nscd/initgrcache.c
new file mode 100644
index 0000000000..4deb483fbb
--- /dev/null
+++ b/REORG.TODO/nscd/initgrcache.c
@@ -0,0 +1,438 @@
+/* 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 <grp.h>
+#include <libintl.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+#include <sys/mman.h>
+#include <scratch_buffer.h>
+
+#include "dbg_log.h"
+#include "nscd.h"
+#ifdef HAVE_SENDFILE
+# include <kernel-features.h>
+#endif
+
+#include "../nss/nsswitch.h"
+
+
+/* Type of the lookup function. */
+typedef enum nss_status (*initgroups_dyn_function) (const char *, gid_t,
+ long int *, long int *,
+ gid_t **, long int, int *);
+
+
+static const initgr_response_header notfound =
+{
+ .version = NSCD_VERSION,
+ .found = 0,
+ .ngrps = 0
+};
+
+
+#include "../grp/compat-initgroups.c"
+
+
+static time_t
+addinitgroupsX (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;
+ initgr_response_header resp;
+ char strdata[0];
+ } *dataset = NULL;
+
+ if (__glibc_unlikely (debug_level > 0))
+ {
+ if (he == NULL)
+ dbg_log (_("Haven't found \"%s\" in group cache!"), (char *) key);
+ else
+ dbg_log (_("Reloading \"%s\" in group cache!"), (char *) key);
+ }
+
+ static service_user *group_database;
+ service_user *nip;
+ int no_more;
+
+ if (group_database == NULL)
+ no_more = __nss_database_lookup ("group", NULL,
+ "compat [NOTFOUND=return] files",
+ &group_database);
+ else
+ no_more = 0;
+ nip = group_database;
+
+ /* We always use sysconf even if NGROUPS_MAX is defined. That way, the
+ limit can be raised in the kernel configuration without having to
+ recompile libc. */
+ long int limit = __sysconf (_SC_NGROUPS_MAX);
+
+ long int size;
+ if (limit > 0)
+ /* We limit the size of the intially allocated array. */
+ size = MIN (limit, 64);
+ else
+ /* No fixed limit on groups. Pick a starting buffer size. */
+ size = 16;
+
+ long int start = 0;
+ bool all_tryagain = true;
+ bool any_success = false;
+
+ /* This is temporary memory, we need not (and must not) call
+ mempool_alloc. */
+ // XXX This really should use alloca. need to change the backends.
+ gid_t *groups = (gid_t *) malloc (size * sizeof (gid_t));
+ if (__glibc_unlikely (groups == NULL))
+ /* No more memory. */
+ goto out;
+
+ /* Nothing added yet. */
+ while (! no_more)
+ {
+ long int prev_start = start;
+ enum nss_status status;
+ initgroups_dyn_function fct;
+ fct = __nss_lookup_function (nip, "initgroups_dyn");
+
+ if (fct == NULL)
+ {
+ status = compat_call (nip, key, -1, &start, &size, &groups,
+ limit, &errno);
+
+ if (nss_next_action (nip, NSS_STATUS_UNAVAIL) != NSS_ACTION_CONTINUE)
+ break;
+ }
+ else
+ status = DL_CALL_FCT (fct, (key, -1, &start, &size, &groups,
+ limit, &errno));
+
+ /* Remove duplicates. */
+ long int cnt = prev_start;
+ while (cnt < start)
+ {
+ long int inner;
+ for (inner = 0; inner < prev_start; ++inner)
+ if (groups[inner] == groups[cnt])
+ break;
+
+ if (inner < prev_start)
+ groups[cnt] = groups[--start];
+ else
+ ++cnt;
+ }
+
+ if (status != NSS_STATUS_TRYAGAIN)
+ all_tryagain = false;
+
+ /* This is really only for debugging. */
+ if (NSS_STATUS_TRYAGAIN > status || status > NSS_STATUS_RETURN)
+ __libc_fatal ("illegal status in internal_getgrouplist");
+
+ any_success |= status == NSS_STATUS_SUCCESS;
+
+ if (status != NSS_STATUS_SUCCESS
+ && nss_next_action (nip, status) == NSS_ACTION_RETURN)
+ break;
+
+ if (nip->next == NULL)
+ no_more = -1;
+ else
+ nip = nip->next;
+ }
+
+ bool all_written;
+ ssize_t total;
+ time_t timeout;
+ out:
+ all_written = true;
+ timeout = MAX_TIMEOUT_VALUE;
+ if (!any_success)
+ {
+ /* Nothing found. Create a negative result record. */
+ total = sizeof (notfound);
+
+ if (he != NULL && all_tryagain)
+ {
+ /* 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) + db->postimeout;
+ }
+ else
+ {
+ /* We have no data. This means we send the standard reply for this
+ case. */
+ if (fd != -1
+ && TEMP_FAILURE_RETRY (send (fd, &notfound, total,
+ MSG_NOSIGNAL)) != total)
+ all_written = false;
+
+ /* If we have a transient error or cannot permanently store
+ the result, so be it. */
+ if (all_tryagain || __builtin_expect (db->negtimeout == 0, 0))
+ {
+ /* Mark the old entry as obsolete. */
+ if (dh != NULL)
+ dh->usable = false;
+ }
+ 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, &notfound, total);
+
+ /* Copy the key data. */
+ char *key_copy = memcpy (dataset->strdata, key, req->key_len);
+
+ /* 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 (struct 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;
+ }
+ }
+ }
+ else
+ {
+
+ total = offsetof (struct dataset, strdata) + start * sizeof (int32_t);
+
+ /* If we refill the cache, first assume the reconrd did not
+ change. Allocate memory on the cache since it is likely
+ discarded anyway. If it turns out to be necessary to have a
+ new record we can still allocate real memory. */
+ bool alloca_used = false;
+ dataset = NULL;
+
+ if (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;
+ }
+
+ 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.ngrps = start;
+
+ char *cp = dataset->strdata;
+
+ /* Copy the GID values. If the size of the types match this is
+ very simple. */
+ if (sizeof (gid_t) == sizeof (int32_t))
+ cp = mempcpy (cp, groups, start * sizeof (gid_t));
+ else
+ {
+ gid_t *gcp = (gid_t *) cp;
+
+ for (int i = 0; i < start; ++i)
+ *gcp++ = groups[i];
+
+ cp = (char *) gcp;
+ }
+
+ /* Finally the user name. */
+ memcpy (cp, key, req->key_len);
+
+ assert (cp == dataset->strdata + total - offsetof (struct dataset,
+ strdata));
+
+ /* 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->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 (newp != NULL)
+ {
+ /* Adjust pointer into the memory block. */
+ cp = (char *) newp + (cp - (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));
+ ssize_t written = sendfileall (fd, db->wr_fd,
+ (char *) &dataset->resp
+ - (char *) db->head,
+ dataset->head.recsize);
+ if (written != dataset->head.recsize)
+ {
+# ifndef __ASSUME_SENDFILE
+ if (written == -1 && errno == ENOSYS)
+ goto use_write;
+# endif
+ all_written = false;
+ }
+ }
+ else
+# ifndef __ASSUME_SENDFILE
+ use_write:
+# endif
+#endif
+ if (writeall (fd, &dataset->resp, dataset->head.recsize)
+ != dataset->head.recsize)
+ all_written = false;
+ }
+
+
+ /* Add the record to the database. But only if it has not been
+ stored on the stack. */
+ if (! 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 (INITGROUPS, cp, req->key_len, &dataset->head, true,
+ db, uid, he == NULL);
+
+ pthread_rwlock_unlock (&db->lock);
+ }
+ }
+
+ free (groups);
+
+ if (__builtin_expect (!all_written, 0) && debug_level > 0)
+ {
+ char buf[256];
+ dbg_log (_("short write in %s: %s"), __FUNCTION__,
+ strerror_r (errno, buf, sizeof (buf)));
+ }
+
+ return timeout;
+}
+
+
+void
+addinitgroups (struct database_dyn *db, int fd, request_header *req, void *key,
+ uid_t uid)
+{
+ addinitgroupsX (db, fd, req, key, uid, NULL, NULL);
+}
+
+
+time_t
+readdinitgroups (struct database_dyn *db, struct hashentry *he,
+ struct datahead *dh)
+{
+ request_header req =
+ {
+ .type = INITGROUPS,
+ .key_len = he->len
+ };
+
+ return addinitgroupsX (db, -1, &req, db->data + he->key, he->owner, he, dh);
+}
diff --git a/REORG.TODO/nscd/mem.c b/REORG.TODO/nscd/mem.c
new file mode 100644
index 0000000000..092f3ae7c1
--- /dev/null
+++ b/REORG.TODO/nscd/mem.c
@@ -0,0 +1,589 @@
+/* Cache memory handling.
+ 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 <error.h>
+#include <fcntl.h>
+#include <inttypes.h>
+#include <libintl.h>
+#include <limits.h>
+#include <obstack.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/mman.h>
+#include <sys/param.h>
+
+#include "dbg_log.h"
+#include "nscd.h"
+
+
+static int
+sort_he (const void *p1, const void *p2)
+{
+ struct hashentry *h1 = *(struct hashentry **) p1;
+ struct hashentry *h2 = *(struct hashentry **) p2;
+
+ if (h1 < h2)
+ return -1;
+ if (h1 > h2)
+ return 1;
+ return 0;
+}
+
+
+static int
+sort_he_data (const void *p1, const void *p2)
+{
+ struct hashentry *h1 = *(struct hashentry **) p1;
+ struct hashentry *h2 = *(struct hashentry **) p2;
+
+ if (h1->packet < h2->packet)
+ return -1;
+ if (h1->packet > h2->packet)
+ return 1;
+ return 0;
+}
+
+
+/* Basic definitions for the bitmap implementation. Only BITMAP_T
+ needs to be changed to choose a different word size. */
+#define BITMAP_T uint8_t
+#define BITS (CHAR_BIT * sizeof (BITMAP_T))
+#define ALLBITS ((((BITMAP_T) 1) << BITS) - 1)
+#define HIGHBIT (((BITMAP_T) 1) << (BITS - 1))
+
+
+static void
+markrange (BITMAP_T *mark, ref_t start, size_t len)
+{
+ /* Adjust parameters for block alignment. */
+ assert ((start & BLOCK_ALIGN_M1) == 0);
+ start /= BLOCK_ALIGN;
+ len = (len + BLOCK_ALIGN_M1) / BLOCK_ALIGN;
+
+ size_t elem = start / BITS;
+
+ if (start % BITS != 0)
+ {
+ if (start % BITS + len <= BITS)
+ {
+ /* All fits in the partial byte. */
+ mark[elem] |= (ALLBITS >> (BITS - len)) << (start % BITS);
+ return;
+ }
+
+ mark[elem++] |= ALLBITS << (start % BITS);
+ len -= BITS - (start % BITS);
+ }
+
+ while (len >= BITS)
+ {
+ mark[elem++] = ALLBITS;
+ len -= BITS;
+ }
+
+ if (len > 0)
+ mark[elem] |= ALLBITS >> (BITS - len);
+}
+
+
+void
+gc (struct database_dyn *db)
+{
+ /* We need write access. */
+ pthread_rwlock_wrlock (&db->lock);
+
+ /* And the memory handling lock. */
+ pthread_mutex_lock (&db->memlock);
+
+ /* We need an array representing the data area. All memory
+ allocation is BLOCK_ALIGN aligned so this is the level at which
+ we have to look at the memory. We use a mark and sweep algorithm
+ where the marks are placed in this array. */
+ assert (db->head->first_free % BLOCK_ALIGN == 0);
+
+ BITMAP_T *mark;
+ bool mark_use_malloc;
+ /* In prune_cache we are also using a dynamically allocated array.
+ If the array in the caller is too large we have malloc'ed it. */
+ size_t stack_used = sizeof (bool) * db->head->module;
+ if (__glibc_unlikely (stack_used > MAX_STACK_USE))
+ stack_used = 0;
+ size_t nmark = (db->head->first_free / BLOCK_ALIGN + BITS - 1) / BITS;
+ size_t memory_needed = nmark * sizeof (BITMAP_T);
+ if (__glibc_likely (stack_used + memory_needed <= MAX_STACK_USE))
+ {
+ mark = (BITMAP_T *) alloca_account (memory_needed, stack_used);
+ mark_use_malloc = false;
+ memset (mark, '\0', memory_needed);
+ }
+ else
+ {
+ mark = (BITMAP_T *) xcalloc (1, memory_needed);
+ mark_use_malloc = true;
+ }
+
+ /* Create an array which can hold pointer to all the entries in hash
+ entries. */
+ memory_needed = 2 * db->head->nentries * sizeof (struct hashentry *);
+ struct hashentry **he;
+ struct hashentry **he_data;
+ bool he_use_malloc;
+ if (__glibc_likely (stack_used + memory_needed <= MAX_STACK_USE))
+ {
+ he = alloca_account (memory_needed, stack_used);
+ he_use_malloc = false;
+ }
+ else
+ {
+ he = xmalloc (memory_needed);
+ he_use_malloc = true;
+ }
+ he_data = &he[db->head->nentries];
+
+ size_t cnt = 0;
+ for (size_t idx = 0; idx < db->head->module; ++idx)
+ {
+ ref_t *prevp = &db->head->array[idx];
+ ref_t run = *prevp;
+
+ while (run != ENDREF)
+ {
+ assert (cnt < db->head->nentries);
+ he[cnt] = (struct hashentry *) (db->data + run);
+
+ he[cnt]->prevp = prevp;
+ prevp = &he[cnt]->next;
+
+ /* This is the hash entry itself. */
+ markrange (mark, run, sizeof (struct hashentry));
+
+ /* Add the information for the data itself. We do this
+ only for the one special entry marked with FIRST. */
+ if (he[cnt]->first)
+ {
+ struct datahead *dh
+ = (struct datahead *) (db->data + he[cnt]->packet);
+ markrange (mark, he[cnt]->packet, dh->allocsize);
+ }
+
+ run = he[cnt]->next;
+
+ ++cnt;
+ }
+ }
+ assert (cnt == db->head->nentries);
+
+ /* Sort the entries by the addresses of the referenced data. All
+ the entries pointing to the same DATAHEAD object will have the
+ same key. Stability of the sorting is unimportant. */
+ memcpy (he_data, he, cnt * sizeof (struct hashentry *));
+ qsort (he_data, cnt, sizeof (struct hashentry *), sort_he_data);
+
+ /* Sort the entries by their address. */
+ qsort (he, cnt, sizeof (struct hashentry *), sort_he);
+
+#define obstack_chunk_alloc xmalloc
+#define obstack_chunk_free free
+ struct obstack ob;
+ obstack_init (&ob);
+
+ /* Determine the highest used address. */
+ size_t high = nmark;
+ while (high > 0 && mark[high - 1] == 0)
+ --high;
+
+ /* No memory used. */
+ if (high == 0)
+ {
+ db->head->first_free = 0;
+ goto out;
+ }
+
+ /* Determine the highest offset. */
+ BITMAP_T mask = HIGHBIT;
+ ref_t highref = (high * BITS - 1) * BLOCK_ALIGN;
+ while ((mark[high - 1] & mask) == 0)
+ {
+ mask >>= 1;
+ highref -= BLOCK_ALIGN;
+ }
+
+ /* Now we can iterate over the MARK array and find bits which are not
+ set. These represent memory which can be recovered. */
+ size_t byte = 0;
+ /* Find the first gap. */
+ while (byte < high && mark[byte] == ALLBITS)
+ ++byte;
+
+ if (byte == high
+ || (byte == high - 1 && (mark[byte] & ~(mask | (mask - 1))) == 0))
+ /* No gap. */
+ goto out;
+
+ mask = 1;
+ cnt = 0;
+ while ((mark[byte] & mask) != 0)
+ {
+ ++cnt;
+ mask <<= 1;
+ }
+ ref_t off_free = (byte * BITS + cnt) * BLOCK_ALIGN;
+ assert (off_free <= db->head->first_free);
+
+ struct hashentry **next_hash = he;
+ struct hashentry **next_data = he_data;
+
+ /* Skip over the hash entries in the first block which does not get
+ moved. */
+ while (next_hash < &he[db->head->nentries]
+ && *next_hash < (struct hashentry *) (db->data + off_free))
+ ++next_hash;
+
+ while (next_data < &he_data[db->head->nentries]
+ && (*next_data)->packet < off_free)
+ ++next_data;
+
+
+ /* Now we start modifying the data. Make sure all readers of the
+ data are aware of this and temporarily don't use the data. */
+ ++db->head->gc_cycle;
+ assert ((db->head->gc_cycle & 1) == 1);
+
+
+ /* We do not perform the move operations right away since the
+ he_data array is not sorted by the address of the data. */
+ struct moveinfo
+ {
+ void *from;
+ void *to;
+ size_t size;
+ struct moveinfo *next;
+ } *moves = NULL;
+
+ while (byte < high)
+ {
+ /* Search for the next filled block. BYTE is the index of the
+ entry in MARK, MASK is the bit, and CNT is the bit number.
+ OFF_FILLED is the corresponding offset. */
+ if ((mark[byte] & ~(mask - 1)) == 0)
+ {
+ /* No other bit set in the same element of MARK. Search in the
+ following memory. */
+ do
+ ++byte;
+ while (byte < high && mark[byte] == 0);
+
+ if (byte == high)
+ /* That was it. */
+ break;
+
+ mask = 1;
+ cnt = 0;
+ }
+ /* Find the exact bit. */
+ while ((mark[byte] & mask) == 0)
+ {
+ ++cnt;
+ mask <<= 1;
+ }
+
+ ref_t off_alloc = (byte * BITS + cnt) * BLOCK_ALIGN;
+ assert (off_alloc <= db->head->first_free);
+
+ /* Find the end of the used area. */
+ if ((mark[byte] & ~(mask - 1)) == (BITMAP_T) ~(mask - 1))
+ {
+ /* All other bits set. Search the next bytes in MARK. */
+ do
+ ++byte;
+ while (byte < high && mark[byte] == ALLBITS);
+
+ mask = 1;
+ cnt = 0;
+ }
+ if (byte < high)
+ {
+ /* Find the exact bit. */
+ while ((mark[byte] & mask) != 0)
+ {
+ ++cnt;
+ mask <<= 1;
+ }
+ }
+
+ ref_t off_allocend = (byte * BITS + cnt) * BLOCK_ALIGN;
+ assert (off_allocend <= db->head->first_free);
+ /* Now we know that we can copy the area from OFF_ALLOC to
+ OFF_ALLOCEND (not included) to the memory starting at
+ OFF_FREE. First fix up all the entries for the
+ displacement. */
+ ref_t disp = off_alloc - off_free;
+
+ struct moveinfo *new_move;
+ if (__builtin_expect (stack_used + sizeof (*new_move) <= MAX_STACK_USE,
+ 1))
+ new_move = alloca_account (sizeof (*new_move), stack_used);
+ else
+ new_move = obstack_alloc (&ob, sizeof (*new_move));
+ new_move->from = db->data + off_alloc;
+ new_move->to = db->data + off_free;
+ new_move->size = off_allocend - off_alloc;
+ /* Create a circular list to be always able to append at the end. */
+ if (moves == NULL)
+ moves = new_move->next = new_move;
+ else
+ {
+ new_move->next = moves->next;
+ moves = moves->next = new_move;
+ }
+
+ /* The following loop will prepare to move this much data. */
+ off_free += off_allocend - off_alloc;
+
+ while (off_alloc < off_allocend)
+ {
+ /* Determine whether the next entry is for a hash entry or
+ the data. */
+ if ((struct hashentry *) (db->data + off_alloc) == *next_hash)
+ {
+ /* Just correct the forward reference. */
+ *(*next_hash++)->prevp -= disp;
+
+ off_alloc += ((sizeof (struct hashentry) + BLOCK_ALIGN_M1)
+ & ~BLOCK_ALIGN_M1);
+ }
+ else
+ {
+ assert (next_data < &he_data[db->head->nentries]);
+ assert ((*next_data)->packet == off_alloc);
+
+ struct datahead *dh = (struct datahead *) (db->data + off_alloc);
+ do
+ {
+ assert ((*next_data)->key >= (*next_data)->packet);
+ assert ((*next_data)->key + (*next_data)->len
+ <= (*next_data)->packet + dh->allocsize);
+
+ (*next_data)->packet -= disp;
+ (*next_data)->key -= disp;
+ ++next_data;
+ }
+ while (next_data < &he_data[db->head->nentries]
+ && (*next_data)->packet == off_alloc);
+
+ off_alloc += (dh->allocsize + BLOCK_ALIGN_M1) & ~BLOCK_ALIGN_M1;
+ }
+ }
+ assert (off_alloc == off_allocend);
+
+ assert (off_alloc <= db->head->first_free);
+ if (off_alloc == db->head->first_free)
+ /* We are done, that was the last block. */
+ break;
+ }
+ assert (next_hash == &he[db->head->nentries]);
+ assert (next_data == &he_data[db->head->nentries]);
+
+ /* Now perform the actual moves. */
+ if (moves != NULL)
+ {
+ struct moveinfo *runp = moves->next;
+ do
+ {
+ assert ((char *) runp->to >= db->data);
+ assert ((char *) runp->to + runp->size
+ <= db->data + db->head->first_free);
+ assert ((char *) runp->from >= db->data);
+ assert ((char *) runp->from + runp->size
+ <= db->data + db->head->first_free);
+
+ /* The regions may overlap. */
+ memmove (runp->to, runp->from, runp->size);
+ runp = runp->next;
+ }
+ while (runp != moves->next);
+
+ if (__glibc_unlikely (debug_level >= 3))
+ dbg_log (_("freed %zu bytes in %s cache"),
+ (size_t) (db->head->first_free
+ - ((char *) moves->to + moves->size - db->data)),
+ dbnames[db - dbs]);
+
+ /* The byte past the end of the last copied block is the next
+ available byte. */
+ db->head->first_free = (char *) moves->to + moves->size - db->data;
+
+ /* Consistency check. */
+ if (__glibc_unlikely (debug_level >= 3))
+ {
+ for (size_t idx = 0; idx < db->head->module; ++idx)
+ {
+ ref_t run = db->head->array[idx];
+ size_t cnt = 0;
+
+ while (run != ENDREF)
+ {
+ if (run + sizeof (struct hashentry) > db->head->first_free)
+ {
+ dbg_log ("entry %zu in hash bucket %zu out of bounds: "
+ "%" PRIu32 "+%zu > %zu\n",
+ cnt, idx, run, sizeof (struct hashentry),
+ (size_t) db->head->first_free);
+ break;
+ }
+
+ struct hashentry *he = (struct hashentry *) (db->data + run);
+
+ if (he->key + he->len > db->head->first_free)
+ dbg_log ("key of entry %zu in hash bucket %zu out of "
+ "bounds: %" PRIu32 "+%zu > %zu\n",
+ cnt, idx, he->key, (size_t) he->len,
+ (size_t) db->head->first_free);
+
+ if (he->packet + sizeof (struct datahead)
+ > db->head->first_free)
+ dbg_log ("packet of entry %zu in hash bucket %zu out of "
+ "bounds: %" PRIu32 "+%zu > %zu\n",
+ cnt, idx, he->packet, sizeof (struct datahead),
+ (size_t) db->head->first_free);
+ else
+ {
+ struct datahead *dh = (struct datahead *) (db->data
+ + he->packet);
+ if (he->packet + dh->allocsize
+ > db->head->first_free)
+ dbg_log ("full key of entry %zu in hash bucket %zu "
+ "out of bounds: %" PRIu32 "+%zu > %zu",
+ cnt, idx, he->packet, (size_t) dh->allocsize,
+ (size_t) db->head->first_free);
+ }
+
+ run = he->next;
+ ++cnt;
+ }
+ }
+ }
+ }
+
+ /* Make sure the data on disk is updated. */
+ if (db->persistent)
+ msync (db->head, db->data + db->head->first_free - (char *) db->head,
+ MS_ASYNC);
+
+
+ /* Now we are done modifying the data. */
+ ++db->head->gc_cycle;
+ assert ((db->head->gc_cycle & 1) == 0);
+
+ /* We are done. */
+ out:
+ pthread_mutex_unlock (&db->memlock);
+ pthread_rwlock_unlock (&db->lock);
+
+ if (he_use_malloc)
+ free (he);
+ if (mark_use_malloc)
+ free (mark);
+
+ obstack_free (&ob, NULL);
+}
+
+
+void *
+mempool_alloc (struct database_dyn *db, size_t len, int data_alloc)
+{
+ /* Make sure LEN is a multiple of our maximum alignment so we can
+ keep track of used memory is multiples of this alignment value. */
+ if ((len & BLOCK_ALIGN_M1) != 0)
+ len += BLOCK_ALIGN - (len & BLOCK_ALIGN_M1);
+
+ if (data_alloc)
+ pthread_rwlock_rdlock (&db->lock);
+
+ pthread_mutex_lock (&db->memlock);
+
+ assert ((db->head->first_free & BLOCK_ALIGN_M1) == 0);
+
+ bool tried_resize = false;
+ void *res;
+ retry:
+ res = db->data + db->head->first_free;
+
+ if (__glibc_unlikely (db->head->first_free + len > db->head->data_size))
+ {
+ if (! tried_resize)
+ {
+ /* Try to resize the database. Grow size of 1/8th. */
+ size_t oldtotal = (sizeof (struct database_pers_head)
+ + roundup (db->head->module * sizeof (ref_t),
+ ALIGN)
+ + db->head->data_size);
+ size_t new_data_size = (db->head->data_size
+ + MAX (2 * len, db->head->data_size / 8));
+ size_t newtotal = (sizeof (struct database_pers_head)
+ + roundup (db->head->module * sizeof (ref_t), ALIGN)
+ + new_data_size);
+ if (newtotal > db->max_db_size)
+ {
+ new_data_size -= newtotal - db->max_db_size;
+ newtotal = db->max_db_size;
+ }
+
+ if (db->mmap_used && newtotal > oldtotal
+ /* We only have to adjust the file size. The new pages
+ become magically available. */
+ && TEMP_FAILURE_RETRY_VAL (posix_fallocate (db->wr_fd, oldtotal,
+ newtotal
+ - oldtotal)) == 0)
+ {
+ db->head->data_size = new_data_size;
+ tried_resize = true;
+ goto retry;
+ }
+ }
+
+ if (data_alloc)
+ pthread_rwlock_unlock (&db->lock);
+
+ if (! db->last_alloc_failed)
+ {
+ dbg_log (_("no more memory for database '%s'"), dbnames[db - dbs]);
+
+ db->last_alloc_failed = true;
+ }
+
+ ++db->head->addfailed;
+
+ /* No luck. */
+ res = NULL;
+ }
+ else
+ {
+ db->head->first_free += len;
+
+ db->last_alloc_failed = false;
+
+ }
+
+ pthread_mutex_unlock (&db->memlock);
+
+ return res;
+}
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, &notfound, 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, &notfound, 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);
+}
diff --git a/REORG.TODO/nscd/nscd-client.h b/REORG.TODO/nscd/nscd-client.h
new file mode 100644
index 0000000000..96170bff1b
--- /dev/null
+++ b/REORG.TODO/nscd/nscd-client.h
@@ -0,0 +1,452 @@
+/* Copyright (c) 1998-2017 Free Software Foundation, Inc.
+ This file is part of the GNU C Library.
+ Contributed by Thorsten Kukuk <kukuk@suse.de>, 1998.
+
+ The GNU C Library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ The GNU C Library 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
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with the GNU C Library; if not, see
+ <http://www.gnu.org/licenses/>. */
+
+/* This file defines everything that client code should need to
+ know to talk to the nscd daemon. */
+
+#ifndef _NSCD_CLIENT_H
+#define _NSCD_CLIENT_H 1
+
+#include <stdbool.h>
+#include <stdint.h>
+#include <string.h>
+#include <time.h>
+#include <sys/types.h>
+#include <atomic.h>
+#include <nscd-types.h>
+#include <sys/uio.h>
+
+
+/* Version number of the daemon interface */
+#define NSCD_VERSION 2
+
+/* Path of the file where the PID of the running system is stored. */
+#define _PATH_NSCDPID "/var/run/nscd/nscd.pid"
+
+/* Path for the Unix domain socket. */
+#define _PATH_NSCDSOCKET "/var/run/nscd/socket"
+
+/* Path for the configuration file. */
+#define _PATH_NSCDCONF "/etc/nscd.conf"
+
+/* Maximum allowed length for the key. */
+#define MAXKEYLEN 1024
+
+
+/* Available services. */
+typedef enum
+{
+ GETPWBYNAME,
+ GETPWBYUID,
+ GETGRBYNAME,
+ GETGRBYGID,
+ GETHOSTBYNAME,
+ GETHOSTBYNAMEv6,
+ GETHOSTBYADDR,
+ GETHOSTBYADDRv6,
+ SHUTDOWN, /* Shut the server down. */
+ GETSTAT, /* Get the server statistic. */
+ INVALIDATE, /* Invalidate one special cache. */
+ GETFDPW,
+ GETFDGR,
+ GETFDHST,
+ GETAI,
+ INITGROUPS,
+ GETSERVBYNAME,
+ GETSERVBYPORT,
+ GETFDSERV,
+ GETNETGRENT,
+ INNETGR,
+ GETFDNETGR,
+ LASTREQ
+} request_type;
+
+
+/* Header common to all requests */
+typedef struct
+{
+ int32_t version; /* Version number of the daemon interface. */
+ request_type type; /* Service requested. */
+ int32_t key_len; /* Key length. */
+} request_header;
+
+
+/* Structure sent in reply to password query. Note that this struct is
+ sent also if the service is disabled or there is no record found. */
+typedef struct
+{
+ int32_t version;
+ int32_t found;
+ nscd_ssize_t pw_name_len;
+ nscd_ssize_t pw_passwd_len;
+ uid_t pw_uid;
+ gid_t pw_gid;
+ nscd_ssize_t pw_gecos_len;
+ nscd_ssize_t pw_dir_len;
+ nscd_ssize_t pw_shell_len;
+} pw_response_header;
+
+
+/* Structure sent in reply to group query. Note that this struct is
+ sent also if the service is disabled or there is no record found. */
+typedef struct
+{
+ int32_t version;
+ int32_t found;
+ nscd_ssize_t gr_name_len;
+ nscd_ssize_t gr_passwd_len;
+ gid_t gr_gid;
+ nscd_ssize_t gr_mem_cnt;
+} gr_response_header;
+
+
+/* Structure sent in reply to host query. Note that this struct is
+ sent also if the service is disabled or there is no record found. */
+typedef struct
+{
+ int32_t version;
+ int32_t found;
+ nscd_ssize_t h_name_len;
+ nscd_ssize_t h_aliases_cnt;
+ int32_t h_addrtype;
+ int32_t h_length;
+ nscd_ssize_t h_addr_list_cnt;
+ int32_t error;
+} hst_response_header;
+
+
+/* Structure sent in reply to addrinfo query. Note that this struct is
+ sent also if the service is disabled or there is no record found. */
+typedef struct
+{
+ int32_t version;
+ int32_t found;
+ nscd_ssize_t naddrs;
+ nscd_ssize_t addrslen;
+ nscd_ssize_t canonlen;
+ int32_t error;
+} ai_response_header;
+
+/* Structure filled in by __nscd_getai. */
+struct nscd_ai_result
+{
+ int naddrs;
+ char *canon;
+ uint8_t *family;
+ char *addrs;
+};
+
+/* Structure sent in reply to initgroups query. Note that this struct is
+ sent also if the service is disabled or there is no record found. */
+typedef struct
+{
+ int32_t version;
+ int32_t found;
+ nscd_ssize_t ngrps;
+} initgr_response_header;
+
+
+/* Structure sent in reply to services query. Note that this struct is
+ sent also if the service is disabled or there is no record found. */
+typedef struct
+{
+ int32_t version;
+ int32_t found;
+ nscd_ssize_t s_name_len;
+ nscd_ssize_t s_proto_len;
+ nscd_ssize_t s_aliases_cnt;
+ int32_t s_port;
+} serv_response_header;
+
+
+/* Structure send in reply to netgroup query. Note that this struct is
+ sent also if the service is disabled or there is no record found. */
+typedef struct
+{
+ int32_t version;
+ int32_t found;
+ nscd_ssize_t nresults;
+ nscd_ssize_t result_len;
+} netgroup_response_header;
+
+typedef struct
+{
+ int32_t version;
+ int32_t found;
+ int32_t result;
+} innetgroup_response_header;
+
+
+/* Type for offsets in data part of database. */
+typedef uint32_t ref_t;
+/* Value for invalid/no reference. */
+#define ENDREF UINT32_MAX
+
+/* Timestamp type. */
+typedef uint64_t nscd_time_t;
+
+/* Maximum timestamp. */
+#define MAX_TIMEOUT_VALUE \
+ (sizeof (time_t) == sizeof (long int) ? LONG_MAX : INT_MAX)
+
+/* Alignment requirement of the beginning of the data region. */
+#define ALIGN 16
+
+
+/* Head of record in data part of database. */
+struct datahead
+{
+ nscd_ssize_t allocsize; /* Allocated Bytes. */
+ nscd_ssize_t recsize; /* Size of the record. */
+ nscd_time_t timeout; /* Time when this entry becomes invalid. */
+ uint8_t notfound; /* Nonzero if data has not been found. */
+ uint8_t nreloads; /* Reloads without use. */
+ uint8_t usable; /* False if the entry must be ignored. */
+ uint8_t unused; /* Unused. */
+ uint32_t ttl; /* TTL value used. */
+
+ /* We need to have the following element aligned for the response
+ header data types and their use in the 'struct dataset' types
+ defined in the XXXcache.c files. */
+ union
+ {
+ pw_response_header pwdata;
+ gr_response_header grdata;
+ hst_response_header hstdata;
+ ai_response_header aidata;
+ initgr_response_header initgrdata;
+ serv_response_header servdata;
+ netgroup_response_header netgroupdata;
+ innetgroup_response_header innetgroupdata;
+ nscd_ssize_t align1;
+ nscd_time_t align2;
+ } data[0];
+};
+
+static inline time_t
+datahead_init_common (struct datahead *head, nscd_ssize_t allocsize,
+ nscd_ssize_t recsize, uint32_t ttl)
+{
+ /* Initialize so that we don't write out junk in uninitialized data to the
+ cache. */
+ memset (head, 0, sizeof (*head));
+
+ head->allocsize = allocsize;
+ head->recsize = recsize;
+ head->usable = true;
+
+ head->ttl = ttl;
+
+ /* Compute and return the timeout time. */
+ return head->timeout = time (NULL) + ttl;
+}
+
+static inline time_t
+datahead_init_pos (struct datahead *head, nscd_ssize_t allocsize,
+ nscd_ssize_t recsize, uint8_t nreloads, uint32_t ttl)
+{
+ time_t ret = datahead_init_common (head, allocsize, recsize, ttl);
+
+ head->notfound = false;
+ head->nreloads = nreloads;
+
+ return ret;
+}
+
+static inline time_t
+datahead_init_neg (struct datahead *head, nscd_ssize_t allocsize,
+ nscd_ssize_t recsize, uint32_t ttl)
+{
+ time_t ret = datahead_init_common (head, allocsize, recsize, ttl);
+
+ /* We don't need to touch nreloads here since it is set to our desired value
+ (0) when we clear the structure. */
+ head->notfound = true;
+
+ return ret;
+}
+
+/* Structure for one hash table entry. */
+struct hashentry
+{
+ request_type type:8; /* Which type of dataset. */
+ bool first; /* True if this was the original key. */
+ nscd_ssize_t len; /* Length of key. */
+ ref_t key; /* Pointer to key. */
+ int32_t owner; /* If secure table, this is the owner. */
+ ref_t next; /* Next entry in this hash bucket list. */
+ ref_t packet; /* Records for the result. */
+ union
+ {
+ struct hashentry *dellist; /* Next record to be deleted. This can be a
+ pointer since only nscd uses this field. */
+ ref_t *prevp; /* Pointer to field containing forward
+ reference. */
+ };
+};
+
+
+/* Current persistent database version. */
+#define DB_VERSION 2
+
+/* Maximum time allowed between updates of the timestamp. */
+#define MAPPING_TIMEOUT (5 * 60)
+
+
+/* Used indices for the EXTRA_DATA element of 'database_pers_head'.
+ Each database has its own indices. */
+#define NSCD_HST_IDX_CONF_TIMESTAMP 0
+
+
+/* Header of persistent database file. */
+struct database_pers_head
+{
+ int32_t version;
+ int32_t header_size;
+ volatile int32_t gc_cycle;
+ volatile int32_t nscd_certainly_running;
+ volatile nscd_time_t timestamp;
+ /* Room for extensions. */
+ volatile uint32_t extra_data[4];
+
+ nscd_ssize_t module;
+ nscd_ssize_t data_size;
+
+ nscd_ssize_t first_free; /* Offset of first free byte in data area. */
+
+ nscd_ssize_t nentries;
+ nscd_ssize_t maxnentries;
+ nscd_ssize_t maxnsearched;
+
+ uint64_t poshit;
+ uint64_t neghit;
+ uint64_t posmiss;
+ uint64_t negmiss;
+
+ uint64_t rdlockdelayed;
+ uint64_t wrlockdelayed;
+
+ uint64_t addfailed;
+
+ ref_t array[0];
+};
+
+
+/* Mapped database record. */
+struct mapped_database
+{
+ const struct database_pers_head *head;
+ const char *data;
+ size_t mapsize;
+ int counter; /* > 0 indicates it is usable. */
+ size_t datasize;
+};
+#define NO_MAPPING ((struct mapped_database *) -1l)
+
+struct locked_map_ptr
+{
+ int lock;
+ struct mapped_database *mapped;
+};
+#define libc_locked_map_ptr(class, name) class struct locked_map_ptr name
+
+/* Try acquiring lock for mapptr, returns true if it succeeds, false
+ if not. */
+static inline bool
+__nscd_acquire_maplock (volatile struct locked_map_ptr *mapptr)
+{
+ int cnt = 0;
+ while (__builtin_expect (atomic_compare_and_exchange_val_acq (&mapptr->lock,
+ 1, 0) != 0, 0))
+ {
+ // XXX Best number of rounds?
+ if (__glibc_unlikely (++cnt > 5))
+ return false;
+
+ atomic_spin_nop ();
+ }
+
+ return true;
+}
+
+
+/* Open socket connection to nscd server. */
+extern int __nscd_open_socket (const char *key, size_t keylen,
+ request_type type, void *response,
+ size_t responselen) attribute_hidden;
+
+/* Try to get a file descriptor for the shared meory segment
+ containing the database. */
+extern struct mapped_database *__nscd_get_mapping (request_type type,
+ const char *key,
+ struct mapped_database **mappedp) attribute_hidden;
+
+/* Get reference of mapping. */
+extern struct mapped_database *__nscd_get_map_ref (request_type type,
+ const char *name,
+ volatile struct locked_map_ptr *mapptr,
+ int *gc_cyclep);
+
+/* Unmap database. */
+extern void __nscd_unmap (struct mapped_database *mapped);
+
+/* Drop reference of mapping. */
+static int
+__attribute__ ((unused))
+__nscd_drop_map_ref (struct mapped_database *map, int *gc_cycle)
+{
+ if (map != NO_MAPPING)
+ {
+ int now_cycle = map->head->gc_cycle;
+ if (__glibc_unlikely (now_cycle != *gc_cycle))
+ {
+ /* We might have read inconsistent data. */
+ *gc_cycle = now_cycle;
+ return -1;
+ }
+
+ if (atomic_decrement_val (&map->counter) == 0)
+ __nscd_unmap (map);
+ }
+
+ return 0;
+}
+
+
+/* Search the mapped database. */
+extern struct datahead *__nscd_cache_search (request_type type,
+ const char *key,
+ size_t keylen,
+ const struct mapped_database *mapped,
+ size_t datalen);
+
+/* Wrappers around read, readv and write that only read/write less than LEN
+ bytes on error or EOF. */
+extern ssize_t __readall (int fd, void *buf, size_t len)
+ attribute_hidden;
+extern ssize_t __readvall (int fd, const struct iovec *iov, int iovcnt)
+ attribute_hidden;
+extern ssize_t writeall (int fd, const void *buf, size_t len)
+ attribute_hidden;
+extern ssize_t sendfileall (int tofd, int fromfd, off_t off, size_t len)
+ attribute_hidden;
+
+/* Get netlink timestamp counter from mapped area or zero. */
+extern uint32_t __nscd_get_nl_timestamp (void);
+
+#endif /* nscd.h */
diff --git a/REORG.TODO/nscd/nscd.c b/REORG.TODO/nscd/nscd.c
new file mode 100644
index 0000000000..69ef41366c
--- /dev/null
+++ b/REORG.TODO/nscd/nscd.c
@@ -0,0 +1,700 @@
+/* Copyright (c) 1998-2017 Free Software Foundation, Inc.
+ This file is part of the GNU C Library.
+ Contributed by Thorsten Kukuk <kukuk@suse.de>, 1998.
+
+ 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/>. */
+
+/* nscd - Name Service Cache Daemon. Caches passwd, group, and hosts. */
+
+#include <argp.h>
+#include <assert.h>
+#include <dirent.h>
+#include <errno.h>
+#include <error.h>
+#include <fcntl.h>
+#include <libintl.h>
+#include <locale.h>
+#include <paths.h>
+#include <pthread.h>
+#include <signal.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <syslog.h>
+#include <unistd.h>
+#include <sys/mman.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/uio.h>
+#include <sys/un.h>
+#include <sys/wait.h>
+#include <stdarg.h>
+
+#include "dbg_log.h"
+#include "nscd.h"
+#include "selinux.h"
+#include "../nss/nsswitch.h"
+#include <device-nrs.h>
+#ifdef HAVE_INOTIFY
+# include <sys/inotify.h>
+#endif
+#include <kernel-features.h>
+
+/* Get libc version number. */
+#include <version.h>
+
+#define PACKAGE _libc_intl_domainname
+
+int do_shutdown;
+int disabled_passwd;
+int disabled_group;
+
+typedef enum
+{
+ /* Running in background as daemon. */
+ RUN_DAEMONIZE,
+ /* Running in foreground but otherwise behave like a daemon,
+ i.e., detach from terminal and use syslog. This allows
+ better integration with services like systemd. */
+ RUN_FOREGROUND,
+ /* Run in foreground in debug mode. */
+ RUN_DEBUG
+} run_modes;
+
+static run_modes run_mode = RUN_DAEMONIZE;
+
+static const char *conffile = _PATH_NSCDCONF;
+
+time_t start_time;
+
+uintptr_t pagesize_m1;
+
+int paranoia;
+time_t restart_time;
+time_t restart_interval = RESTART_INTERVAL;
+const char *oldcwd;
+uid_t old_uid;
+gid_t old_gid;
+
+static int check_pid (const char *file);
+static int write_pid (const char *file);
+static int monitor_child (int fd);
+
+/* Name and version of program. */
+static void print_version (FILE *stream, struct argp_state *state);
+void (*argp_program_version_hook) (FILE *, struct argp_state *) = print_version;
+
+/* Function to print some extra text in the help message. */
+static char *more_help (int key, const char *text, void *input);
+
+/* Definitions of arguments for argp functions. */
+static const struct argp_option options[] =
+{
+ { "config-file", 'f', N_("NAME"), 0,
+ N_("Read configuration data from NAME") },
+ { "debug", 'd', NULL, 0,
+ N_("Do not fork and display messages on the current tty") },
+ { "foreground", 'F', NULL, 0,
+ N_("Do not fork, but otherwise behave like a daemon") },
+ { "nthreads", 't', N_("NUMBER"), 0, N_("Start NUMBER threads") },
+ { "shutdown", 'K', NULL, 0, N_("Shut the server down") },
+ { "statistics", 'g', NULL, 0, N_("Print current configuration statistics") },
+ { "invalidate", 'i', N_("TABLE"), 0,
+ N_("Invalidate the specified cache") },
+ { "secure", 'S', N_("TABLE,yes"), OPTION_HIDDEN,
+ N_("Use separate cache for each user")},
+ { NULL, 0, NULL, 0, NULL }
+};
+
+/* Short description of program. */
+static const char doc[] = N_("Name Service Cache Daemon.");
+
+/* Prototype for option handler. */
+static error_t parse_opt (int key, char *arg, struct argp_state *state);
+
+/* Data structure to communicate with argp functions. */
+static struct argp argp =
+{
+ options, parse_opt, NULL, doc, NULL, more_help
+};
+
+/* True if only statistics are requested. */
+static bool get_stats;
+static int parent_fd = -1;
+
+int
+main (int argc, char **argv)
+{
+ int remaining;
+
+ /* Set locale via LC_ALL. */
+ setlocale (LC_ALL, "");
+ /* Set the text message domain. */
+ textdomain (PACKAGE);
+
+ /* Determine if the kernel has SELinux support. */
+ nscd_selinux_enabled (&selinux_enabled);
+
+ /* Parse and process arguments. */
+ argp_parse (&argp, argc, argv, 0, &remaining, NULL);
+
+ if (remaining != argc)
+ {
+ error (0, 0, gettext ("wrong number of arguments"));
+ argp_help (&argp, stdout, ARGP_HELP_SEE, program_invocation_short_name);
+ exit (1);
+ }
+
+ /* Read the configuration file. */
+ if (nscd_parse_file (conffile, dbs) != 0)
+ /* We couldn't read the configuration file. We don't start the
+ server. */
+ error (EXIT_FAILURE, 0,
+ _("failure while reading configuration file; this is fatal"));
+
+ /* Do we only get statistics? */
+ if (get_stats)
+ /* Does not return. */
+ receive_print_stats ();
+
+ /* Check if we are already running. */
+ if (check_pid (_PATH_NSCDPID))
+ error (EXIT_FAILURE, 0, _("already running"));
+
+ /* Remember when we started. */
+ start_time = time (NULL);
+
+ /* Determine page size. */
+ pagesize_m1 = getpagesize () - 1;
+
+ if (run_mode == RUN_DAEMONIZE || run_mode == RUN_FOREGROUND)
+ {
+ int i;
+ pid_t pid;
+
+ /* Behave like a daemon. */
+ if (run_mode == RUN_DAEMONIZE)
+ {
+ int fd[2];
+
+ if (pipe (fd) != 0)
+ error (EXIT_FAILURE, errno,
+ _("cannot create a pipe to talk to the child"));
+
+ pid = fork ();
+ if (pid == -1)
+ error (EXIT_FAILURE, errno, _("cannot fork"));
+ if (pid != 0)
+ {
+ /* The parent only reads from the child. */
+ close (fd[1]);
+ exit (monitor_child (fd[0]));
+ }
+ else
+ {
+ /* The child only writes to the parent. */
+ close (fd[0]);
+ parent_fd = fd[1];
+ }
+ }
+
+ int nullfd = open (_PATH_DEVNULL, O_RDWR);
+ if (nullfd != -1)
+ {
+ struct stat64 st;
+
+ if (fstat64 (nullfd, &st) == 0 && S_ISCHR (st.st_mode) != 0
+#if defined DEV_NULL_MAJOR && defined DEV_NULL_MINOR
+ && st.st_rdev == makedev (DEV_NULL_MAJOR, DEV_NULL_MINOR)
+#endif
+ )
+ {
+ /* It is the /dev/null special device alright. */
+ (void) dup2 (nullfd, STDIN_FILENO);
+ (void) dup2 (nullfd, STDOUT_FILENO);
+ (void) dup2 (nullfd, STDERR_FILENO);
+
+ if (nullfd > 2)
+ close (nullfd);
+ }
+ else
+ {
+ /* Ugh, somebody is trying to play a trick on us. */
+ close (nullfd);
+ nullfd = -1;
+ }
+ }
+ int min_close_fd = nullfd == -1 ? 0 : STDERR_FILENO + 1;
+
+ DIR *d = opendir ("/proc/self/fd");
+ if (d != NULL)
+ {
+ struct dirent64 *dirent;
+ int dfdn = dirfd (d);
+
+ while ((dirent = readdir64 (d)) != NULL)
+ {
+ char *endp;
+ long int fdn = strtol (dirent->d_name, &endp, 10);
+
+ if (*endp == '\0' && fdn != dfdn && fdn >= min_close_fd
+ && fdn != parent_fd)
+ close ((int) fdn);
+ }
+
+ closedir (d);
+ }
+ else
+ for (i = min_close_fd; i < getdtablesize (); i++)
+ if (i != parent_fd)
+ close (i);
+
+ setsid ();
+
+ if (chdir ("/") != 0)
+ do_exit (EXIT_FAILURE, errno,
+ _("cannot change current working directory to \"/\""));
+
+ openlog ("nscd", LOG_CONS | LOG_ODELAY, LOG_DAEMON);
+
+ if (write_pid (_PATH_NSCDPID) < 0)
+ dbg_log ("%s: %s", _PATH_NSCDPID, strerror (errno));
+
+ if (!init_logfile ())
+ dbg_log (_("Could not create log file"));
+
+ /* Ignore job control signals. */
+ signal (SIGTTOU, SIG_IGN);
+ signal (SIGTTIN, SIG_IGN);
+ signal (SIGTSTP, SIG_IGN);
+ }
+ else
+ /* In debug mode we are not paranoid. */
+ paranoia = 0;
+
+ signal (SIGINT, termination_handler);
+ signal (SIGQUIT, termination_handler);
+ signal (SIGTERM, termination_handler);
+ signal (SIGPIPE, SIG_IGN);
+
+ /* Cleanup files created by a previous 'bind'. */
+ unlink (_PATH_NSCDSOCKET);
+
+#ifdef HAVE_INOTIFY
+ /* Use inotify to recognize changed files. */
+ inotify_fd = inotify_init1 (IN_NONBLOCK);
+# ifndef __ASSUME_IN_NONBLOCK
+ if (inotify_fd == -1 && errno == ENOSYS)
+ {
+ inotify_fd = inotify_init ();
+ if (inotify_fd != -1)
+ fcntl (inotify_fd, F_SETFL, O_RDONLY | O_NONBLOCK);
+ }
+# endif
+#endif
+
+#ifdef USE_NSCD
+ /* Make sure we do not get recursive calls. */
+ __nss_disable_nscd (register_traced_file);
+#endif
+
+ /* Init databases. */
+ nscd_init ();
+
+ /* Start the SELinux AVC. */
+ if (selinux_enabled)
+ nscd_avc_init ();
+
+ /* Handle incoming requests */
+ start_threads ();
+
+ return 0;
+}
+
+
+static void __attribute__ ((noreturn))
+invalidate_db (const char *dbname)
+{
+ int sock = nscd_open_socket ();
+
+ if (sock == -1)
+ exit (EXIT_FAILURE);
+
+ size_t dbname_len = strlen (dbname) + 1;
+ size_t reqlen = sizeof (request_header) + dbname_len;
+ struct
+ {
+ request_header req;
+ char dbname[];
+ } *reqdata = alloca (reqlen);
+
+ reqdata->req.key_len = dbname_len;
+ reqdata->req.version = NSCD_VERSION;
+ reqdata->req.type = INVALIDATE;
+ memcpy (reqdata->dbname, dbname, dbname_len);
+
+ ssize_t nbytes = TEMP_FAILURE_RETRY (send (sock, reqdata, reqlen,
+ MSG_NOSIGNAL));
+
+ if (nbytes != reqlen)
+ {
+ int err = errno;
+ close (sock);
+ error (EXIT_FAILURE, err, _("write incomplete"));
+ }
+
+ /* Wait for ack. Older nscd just closed the socket when
+ prune_cache finished, silently ignore that. */
+ int32_t resp = 0;
+ nbytes = TEMP_FAILURE_RETRY (read (sock, &resp, sizeof (resp)));
+ if (nbytes != 0 && nbytes != sizeof (resp))
+ {
+ int err = errno;
+ close (sock);
+ error (EXIT_FAILURE, err, _("cannot read invalidate ACK"));
+ }
+
+ close (sock);
+
+ if (resp != 0)
+ error (EXIT_FAILURE, resp, _("invalidation failed"));
+
+ exit (0);
+}
+
+static void __attribute__ ((noreturn))
+send_shutdown (void)
+{
+ int sock = nscd_open_socket ();
+
+ if (sock == -1)
+ exit (EXIT_FAILURE);
+
+ request_header req;
+ req.version = NSCD_VERSION;
+ req.type = SHUTDOWN;
+ req.key_len = 0;
+
+ ssize_t nbytes = TEMP_FAILURE_RETRY (send (sock, &req, sizeof req,
+ MSG_NOSIGNAL));
+ close (sock);
+ exit (nbytes != sizeof (request_header) ? EXIT_FAILURE : EXIT_SUCCESS);
+}
+
+/* Handle program arguments. */
+static error_t
+parse_opt (int key, char *arg, struct argp_state *state)
+{
+ switch (key)
+ {
+ case 'd':
+ ++debug_level;
+ run_mode = RUN_DEBUG;
+ break;
+
+ case 'F':
+ run_mode = RUN_FOREGROUND;
+ break;
+
+ case 'f':
+ conffile = arg;
+ break;
+
+ case 'K':
+ if (getuid () != 0)
+ error (4, 0, _("Only root is allowed to use this option!"));
+ else
+ send_shutdown ();
+ break;
+
+ case 'g':
+ get_stats = true;
+ break;
+
+ case 'i':
+ {
+ /* Validate the database name. */
+
+ dbtype cnt;
+ for (cnt = pwddb; cnt < lastdb; ++cnt)
+ if (strcmp (arg, dbnames[cnt]) == 0)
+ break;
+
+ if (cnt == lastdb)
+ {
+ argp_error (state, _("'%s' is not a known database"), arg);
+ return EINVAL;
+ }
+ }
+ if (getuid () != 0)
+ error (4, 0, _("Only root is allowed to use this option!"));
+ else
+ invalidate_db (arg);
+ break;
+
+ case 't':
+ nthreads = atol (arg);
+ break;
+
+ case 'S':
+ error (0, 0, _("secure services not implemented anymore"));
+ break;
+
+ default:
+ return ARGP_ERR_UNKNOWN;
+ }
+
+ return 0;
+}
+
+/* Print bug-reporting information in the help message. */
+static char *
+more_help (int key, const char *text, void *input)
+{
+ switch (key)
+ {
+ case ARGP_KEY_HELP_EXTRA:
+ {
+ /* We print some extra information. */
+
+ char *tables = xstrdup (dbnames[0]);
+ for (dbtype i = 1; i < lastdb; ++i)
+ {
+ char *more_tables;
+ if (asprintf (&more_tables, "%s %s", tables, dbnames[i]) < 0)
+ more_tables = NULL;
+ free (tables);
+ if (more_tables == NULL)
+ return NULL;
+ tables = more_tables;
+ }
+
+ char *tp;
+ if (asprintf (&tp, gettext ("\
+Supported tables:\n\
+%s\n\
+\n\
+For bug reporting instructions, please see:\n\
+%s.\n\
+"), tables, REPORT_BUGS_TO) < 0)
+ tp = NULL;
+ free (tables);
+ return tp;
+ }
+
+ default:
+ break;
+ }
+
+ return (char *) text;
+}
+
+/* Print the version information. */
+static void
+print_version (FILE *stream, struct argp_state *state)
+{
+ fprintf (stream, "nscd %s%s\n", PKGVERSION, VERSION);
+ fprintf (stream, gettext ("\
+Copyright (C) %s Free Software Foundation, Inc.\n\
+This is free software; see the source for copying conditions. There is NO\n\
+warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\
+"), "2017");
+ fprintf (stream, gettext ("Written by %s.\n"),
+ "Thorsten Kukuk and Ulrich Drepper");
+}
+
+
+/* Create a socket connected to a name. */
+int
+nscd_open_socket (void)
+{
+ struct sockaddr_un addr;
+ int sock;
+
+ sock = socket (PF_UNIX, SOCK_STREAM, 0);
+ if (sock < 0)
+ return -1;
+
+ addr.sun_family = AF_UNIX;
+ assert (sizeof (addr.sun_path) >= sizeof (_PATH_NSCDSOCKET));
+ strcpy (addr.sun_path, _PATH_NSCDSOCKET);
+ if (connect (sock, (struct sockaddr *) &addr, sizeof (addr)) < 0)
+ {
+ close (sock);
+ return -1;
+ }
+
+ return sock;
+}
+
+
+/* Cleanup. */
+void
+termination_handler (int signum)
+{
+ close_sockets ();
+
+ /* Clean up the file created by 'bind'. */
+ unlink (_PATH_NSCDSOCKET);
+
+ /* Clean up pid file. */
+ unlink (_PATH_NSCDPID);
+
+ // XXX Terminate threads.
+
+ /* Synchronize memory. */
+ for (int cnt = 0; cnt < lastdb; ++cnt)
+ {
+ if (!dbs[cnt].enabled || dbs[cnt].head == NULL)
+ continue;
+
+ /* Make sure nobody keeps using the database. */
+ dbs[cnt].head->timestamp = 0;
+
+ if (dbs[cnt].persistent)
+ // XXX async OK?
+ msync (dbs[cnt].head, dbs[cnt].memsize, MS_ASYNC);
+ }
+
+ _exit (EXIT_SUCCESS);
+}
+
+/* Returns 1 if the process in pid file FILE is running, 0 if not. */
+static int
+check_pid (const char *file)
+{
+ FILE *fp;
+
+ fp = fopen (file, "r");
+ if (fp)
+ {
+ pid_t pid;
+ int n;
+
+ n = fscanf (fp, "%d", &pid);
+ fclose (fp);
+
+ /* If we cannot parse the file default to assuming nscd runs.
+ If the PID is alive, assume it is running. That all unless
+ the PID is the same as the current process' since tha latter
+ can mean we re-exec. */
+ if ((n != 1 || kill (pid, 0) == 0) && pid != getpid ())
+ return 1;
+ }
+
+ return 0;
+}
+
+/* Write the current process id to the file FILE.
+ Returns 0 if successful, -1 if not. */
+static int
+write_pid (const char *file)
+{
+ FILE *fp;
+
+ fp = fopen (file, "w");
+ if (fp == NULL)
+ return -1;
+
+ fprintf (fp, "%d\n", getpid ());
+
+ int result = fflush (fp) || ferror (fp) ? -1 : 0;
+
+ fclose (fp);
+
+ return result;
+}
+
+static int
+monitor_child (int fd)
+{
+ int child_ret = 0;
+ int ret = read (fd, &child_ret, sizeof (child_ret));
+
+ /* The child terminated with an error, either via exit or some other abnormal
+ method, like a segfault. */
+ if (ret <= 0 || child_ret != 0)
+ {
+ int status;
+ int err = wait (&status);
+
+ if (err < 0)
+ {
+ fprintf (stderr, _("'wait' failed\n"));
+ return 1;
+ }
+
+ if (WIFEXITED (status))
+ {
+ child_ret = WEXITSTATUS (status);
+ fprintf (stderr, _("child exited with status %d\n"), child_ret);
+ }
+ if (WIFSIGNALED (status))
+ {
+ child_ret = WTERMSIG (status);
+ fprintf (stderr, _("child terminated by signal %d\n"), child_ret);
+ }
+ }
+
+ /* We have the child status, so exit with that code. */
+ close (fd);
+
+ return child_ret;
+}
+
+void
+do_exit (int child_ret, int errnum, const char *format, ...)
+{
+ if (parent_fd != -1)
+ {
+ int ret __attribute__ ((unused));
+ ret = write (parent_fd, &child_ret, sizeof (child_ret));
+ assert (ret == sizeof (child_ret));
+ close (parent_fd);
+ }
+
+ if (format != NULL)
+ {
+ /* Emulate error() since we don't have a va_list variant for it. */
+ va_list argp;
+
+ fflush (stdout);
+
+ fprintf (stderr, "%s: ", program_invocation_name);
+
+ va_start (argp, format);
+ vfprintf (stderr, format, argp);
+ va_end (argp);
+
+ fprintf (stderr, ": %s\n", strerror (errnum));
+ fflush (stderr);
+ }
+
+ /* Finally, exit. */
+ exit (child_ret);
+}
+
+void
+notify_parent (int child_ret)
+{
+ if (parent_fd == -1)
+ return;
+
+ int ret __attribute__ ((unused));
+ ret = write (parent_fd, &child_ret, sizeof (child_ret));
+ assert (ret == sizeof (child_ret));
+ close (parent_fd);
+ parent_fd = -1;
+}
diff --git a/REORG.TODO/nscd/nscd.conf b/REORG.TODO/nscd/nscd.conf
new file mode 100644
index 0000000000..39b875912d
--- /dev/null
+++ b/REORG.TODO/nscd/nscd.conf
@@ -0,0 +1,88 @@
+#
+# /etc/nscd.conf
+#
+# An example Name Service Cache config file. This file is needed by nscd.
+#
+# Legal entries are:
+#
+# logfile <file>
+# debug-level <level>
+# threads <initial #threads to use>
+# max-threads <maximum #threads to use>
+# server-user <user to run server as instead of root>
+# server-user is ignored if nscd is started with -S parameters
+# stat-user <user who is allowed to request statistics>
+# reload-count unlimited|<number>
+# paranoia <yes|no>
+# restart-interval <time in seconds>
+#
+# enable-cache <service> <yes|no>
+# positive-time-to-live <service> <time in seconds>
+# negative-time-to-live <service> <time in seconds>
+# suggested-size <service> <prime number>
+# check-files <service> <yes|no>
+# persistent <service> <yes|no>
+# shared <service> <yes|no>
+# max-db-size <service> <number bytes>
+# auto-propagate <service> <yes|no>
+#
+# Currently supported cache names (services): passwd, group, hosts, services
+#
+
+
+# logfile /var/log/nscd.log
+# threads 4
+# max-threads 32
+# server-user nobody
+# stat-user somebody
+ debug-level 0
+# reload-count 5
+ paranoia no
+# restart-interval 3600
+
+ enable-cache passwd yes
+ positive-time-to-live passwd 600
+ negative-time-to-live passwd 20
+ suggested-size passwd 211
+ check-files passwd yes
+ persistent passwd yes
+ shared passwd yes
+ max-db-size passwd 33554432
+ auto-propagate passwd yes
+
+ enable-cache group yes
+ positive-time-to-live group 3600
+ negative-time-to-live group 60
+ suggested-size group 211
+ check-files group yes
+ persistent group yes
+ shared group yes
+ max-db-size group 33554432
+ auto-propagate group yes
+
+ enable-cache hosts yes
+ positive-time-to-live hosts 3600
+ negative-time-to-live hosts 20
+ suggested-size hosts 211
+ check-files hosts yes
+ persistent hosts yes
+ shared hosts yes
+ max-db-size hosts 33554432
+
+ enable-cache services yes
+ positive-time-to-live services 28800
+ negative-time-to-live services 20
+ suggested-size services 211
+ check-files services yes
+ persistent services yes
+ shared services yes
+ max-db-size services 33554432
+
+ enable-cache netgroup yes
+ positive-time-to-live netgroup 28800
+ negative-time-to-live netgroup 20
+ suggested-size netgroup 211
+ check-files netgroup yes
+ persistent netgroup yes
+ shared netgroup yes
+ max-db-size netgroup 33554432
diff --git a/REORG.TODO/nscd/nscd.h b/REORG.TODO/nscd/nscd.h
new file mode 100644
index 0000000000..c6b0a3c836
--- /dev/null
+++ b/REORG.TODO/nscd/nscd.h
@@ -0,0 +1,377 @@
+/* Copyright (c) 1998-2017 Free Software Foundation, Inc.
+ This file is part of the GNU C Library.
+ Contributed by Thorsten Kukuk <kukuk@suse.de>, 1998.
+
+ The GNU C Library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ The GNU C Library 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
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with the GNU C Library; if not, see
+ <http://www.gnu.org/licenses/>. */
+
+#ifndef _NSCD_H
+#define _NSCD_H 1
+
+#include <pthread.h>
+#include <stdbool.h>
+#include <time.h>
+#include <sys/uio.h>
+
+/* The declarations for the request and response types are in the file
+ "nscd-client.h", which should contain everything needed by client
+ functions. */
+#include "nscd-client.h"
+
+
+/* Handle databases. */
+typedef enum
+{
+ pwddb,
+ grpdb,
+ hstdb,
+ servdb,
+ netgrdb,
+ lastdb
+} dbtype;
+
+
+/* Default limit on the number of times a value gets reloaded without
+ being used in the meantime. NSCD does not throw a value out as
+ soon as it times out. It tries to reload the value from the
+ server. Only if the value has not been used for so many rounds it
+ is removed. */
+#define DEFAULT_RELOAD_LIMIT 5
+
+
+/* Time before restarting the process in paranoia mode. */
+#define RESTART_INTERVAL (60 * 60)
+
+
+/* Stack size for worker threads. */
+#define NSCD_THREAD_STACKSIZE 1024 * 1024 * (sizeof (void *) / 4)
+
+/* Maximum size of stack frames we allow the thread to use. We use
+ 80% of the thread stack size. */
+#define MAX_STACK_USE ((8 * NSCD_THREAD_STACKSIZE) / 10)
+
+/* Records the file registered per database that when changed
+ or modified requires invalidating the database. */
+struct traced_file
+{
+ /* Tracks the last modified time of the traced file. */
+ time_t mtime;
+ /* Support multiple registered files per database. */
+ struct traced_file *next;
+ int call_res_init;
+ /* Requires Inotify support to do anything useful. */
+#define TRACED_FILE 0
+#define TRACED_DIR 1
+ int inotify_descr[2];
+# ifndef PATH_MAX
+# define PATH_MAX 1024
+# endif
+ /* The parent directory is used to scan for creation/deletion. */
+ char dname[PATH_MAX];
+ /* Just the name of the file with no directory component. */
+ char *sfname;
+ /* The full-path name of the registered file. */
+ char fname[];
+};
+
+/* Initialize a `struct traced_file`. As input we need the name
+ of the file, and if invalidation requires calling res_init.
+ If CRINIT is 1 then res_init will be called after invalidation
+ or if the traced file is changed in any way, otherwise it will
+ not. */
+static inline void
+init_traced_file(struct traced_file *file, const char *fname, int crinit)
+{
+ char *dname;
+ file->mtime = 0;
+ file->inotify_descr[TRACED_FILE] = -1;
+ file->inotify_descr[TRACED_DIR] = -1;
+ strcpy (file->fname, fname);
+ /* Compute the parent directory name and store a copy. The copy makes
+ it much faster to add/remove watches while nscd is running instead
+ of computing this over and over again in a temp buffer. */
+ file->dname[0] = '\0';
+ dname = strrchr (fname, '/');
+ if (dname != NULL)
+ {
+ size_t len = (size_t)(dname - fname);
+ if (len > sizeof (file->dname))
+ abort ();
+ strncpy (file->dname, file->fname, len);
+ file->dname[len] = '\0';
+ }
+ /* The basename is the name just after the last forward slash. */
+ file->sfname = &dname[1];
+ file->call_res_init = crinit;
+}
+
+#define define_traced_file(id, filename) \
+static union \
+{ \
+ struct traced_file file; \
+ char buf[sizeof (struct traced_file) + sizeof (filename)]; \
+} id##_traced_file;
+
+/* Structure describing dynamic part of one database. */
+struct database_dyn
+{
+ pthread_rwlock_t lock;
+ pthread_cond_t prune_cond;
+ pthread_mutex_t prune_lock;
+ pthread_mutex_t prune_run_lock;
+ time_t wakeup_time;
+
+ int enabled;
+ int check_file;
+ int clear_cache;
+ int persistent;
+ int shared;
+ int propagate;
+ struct traced_file *traced_files;
+ const char *db_filename;
+ size_t suggested_module;
+ size_t max_db_size;
+
+ unsigned long int postimeout; /* In seconds. */
+ unsigned long int negtimeout; /* In seconds. */
+
+ int wr_fd; /* Writable file descriptor. */
+ int ro_fd; /* Unwritable file descriptor. */
+
+ const struct iovec *disabled_iov;
+
+ struct database_pers_head *head;
+ char *data;
+ size_t memsize;
+ pthread_mutex_t memlock;
+ bool mmap_used;
+ bool last_alloc_failed;
+};
+
+
+/* Paths of the file for the persistent storage. */
+#define _PATH_NSCD_PASSWD_DB "/var/db/nscd/passwd"
+#define _PATH_NSCD_GROUP_DB "/var/db/nscd/group"
+#define _PATH_NSCD_HOSTS_DB "/var/db/nscd/hosts"
+#define _PATH_NSCD_SERVICES_DB "/var/db/nscd/services"
+#define _PATH_NSCD_NETGROUP_DB "/var/db/nscd/netgroup"
+
+/* Path used when not using persistent storage. */
+#define _PATH_NSCD_XYZ_DB_TMP "/var/run/nscd/dbXXXXXX"
+
+/* Maximum alignment requirement we will encounter. */
+#define BLOCK_ALIGN_LOG 3
+#define BLOCK_ALIGN (1 << BLOCK_ALIGN_LOG)
+#define BLOCK_ALIGN_M1 (BLOCK_ALIGN - 1)
+
+/* Default value for the maximum size of the database files. */
+#define DEFAULT_MAX_DB_SIZE (32 * 1024 * 1024)
+
+/* Number of bytes of data we initially reserve for each hash table bucket. */
+#define DEFAULT_DATASIZE_PER_BUCKET 1024
+
+/* Default module of hash table. */
+#define DEFAULT_SUGGESTED_MODULE 211
+
+
+/* Number of seconds between two cache pruning runs if we do not have
+ better information when it is really needed. */
+#define CACHE_PRUNE_INTERVAL 15
+
+
+/* Global variables. */
+extern struct database_dyn dbs[lastdb] attribute_hidden;
+extern const char *const dbnames[lastdb];
+extern const char *const serv2str[LASTREQ];
+
+extern const struct iovec pwd_iov_disabled;
+extern const struct iovec grp_iov_disabled;
+extern const struct iovec hst_iov_disabled;
+extern const struct iovec serv_iov_disabled;
+extern const struct iovec netgroup_iov_disabled;
+
+
+/* Initial number of threads to run. */
+extern int nthreads;
+/* Maximum number of threads to use. */
+extern int max_nthreads;
+
+/* Inotify descriptor. */
+extern int inotify_fd;
+
+/* User name to run server processes as. */
+extern const char *server_user;
+
+/* Name and UID of user who is allowed to request statistics. */
+extern const char *stat_user;
+extern uid_t stat_uid;
+
+/* Time the server was started. */
+extern time_t start_time;
+
+/* Number of times clients had to wait. */
+extern unsigned long int client_queued;
+
+/* Maximum needed alignment. */
+extern const size_t block_align;
+
+/* Number of times a value is reloaded without being used. UINT_MAX
+ means unlimited. */
+extern unsigned int reload_count;
+
+/* Pagesize minus one. */
+extern uintptr_t pagesize_m1;
+
+/* Nonzero if paranoia mode is enabled. */
+extern int paranoia;
+/* Time after which the process restarts. */
+extern time_t restart_time;
+/* How much time between restarts. */
+extern time_t restart_interval;
+/* Old current working directory. */
+extern const char *oldcwd;
+/* Old user and group ID. */
+extern uid_t old_uid;
+extern gid_t old_gid;
+
+
+/* Prototypes for global functions. */
+
+/* Wrapper functions with error checking for standard functions. */
+#include <programs/xmalloc.h>
+
+/* nscd.c */
+extern void termination_handler (int signum) __attribute__ ((__noreturn__));
+extern int nscd_open_socket (void);
+void notify_parent (int child_ret);
+void do_exit (int child_ret, int errnum, const char *format, ...);
+
+/* connections.c */
+extern void nscd_init (void);
+extern void register_traced_file (size_t dbidx, struct traced_file *finfo);
+#ifdef HAVE_INOTIFY
+extern void install_watches (struct traced_file *finfo);
+#endif
+extern void close_sockets (void);
+extern void start_threads (void) __attribute__ ((__noreturn__));
+
+/* nscd_conf.c */
+extern int nscd_parse_file (const char *fname,
+ struct database_dyn dbs[lastdb]);
+
+/* nscd_stat.c */
+extern void send_stats (int fd, struct database_dyn dbs[lastdb]);
+extern int receive_print_stats (void) __attribute__ ((__noreturn__));
+
+/* cache.c */
+extern struct datahead *cache_search (request_type, const void *key,
+ size_t len, struct database_dyn *table,
+ uid_t owner);
+extern int cache_add (int type, const void *key, size_t len,
+ struct datahead *packet, bool first,
+ struct database_dyn *table, uid_t owner,
+ bool prune_wakeup);
+extern time_t prune_cache (struct database_dyn *table, time_t now, int fd);
+
+/* pwdcache.c */
+extern void addpwbyname (struct database_dyn *db, int fd, request_header *req,
+ void *key, uid_t uid);
+extern void addpwbyuid (struct database_dyn *db, int fd, request_header *req,
+ void *key, uid_t uid);
+extern time_t readdpwbyname (struct database_dyn *db, struct hashentry *he,
+ struct datahead *dh);
+extern time_t readdpwbyuid (struct database_dyn *db, struct hashentry *he,
+ struct datahead *dh);
+
+/* grpcache.c */
+extern void addgrbyname (struct database_dyn *db, int fd, request_header *req,
+ void *key, uid_t uid);
+extern void addgrbygid (struct database_dyn *db, int fd, request_header *req,
+ void *key, uid_t uid);
+extern time_t readdgrbyname (struct database_dyn *db, struct hashentry *he,
+ struct datahead *dh);
+extern time_t readdgrbygid (struct database_dyn *db, struct hashentry *he,
+ struct datahead *dh);
+
+/* hstcache.c */
+extern void addhstbyname (struct database_dyn *db, int fd, request_header *req,
+ void *key, uid_t uid);
+extern void addhstbyaddr (struct database_dyn *db, int fd, request_header *req,
+ void *key, uid_t uid);
+extern void addhstbynamev6 (struct database_dyn *db, int fd,
+ request_header *req, void *key, uid_t uid);
+extern void addhstbyaddrv6 (struct database_dyn *db, int fd,
+ request_header *req, void *key, uid_t uid);
+extern time_t readdhstbyname (struct database_dyn *db, struct hashentry *he,
+ struct datahead *dh);
+extern time_t readdhstbyaddr (struct database_dyn *db, struct hashentry *he,
+ struct datahead *dh);
+extern time_t readdhstbynamev6 (struct database_dyn *db, struct hashentry *he,
+ struct datahead *dh);
+extern time_t readdhstbyaddrv6 (struct database_dyn *db, struct hashentry *he,
+ struct datahead *dh);
+
+/* aicache.c */
+extern void addhstai (struct database_dyn *db, int fd, request_header *req,
+ void *key, uid_t uid);
+extern time_t readdhstai (struct database_dyn *db, struct hashentry *he,
+ struct datahead *dh);
+
+
+/* initgrcache.c */
+extern void addinitgroups (struct database_dyn *db, int fd,
+ request_header *req, void *key, uid_t uid);
+extern time_t readdinitgroups (struct database_dyn *db, struct hashentry *he,
+ struct datahead *dh);
+
+/* servicecache.c */
+extern void addservbyname (struct database_dyn *db, int fd,
+ request_header *req, void *key, uid_t uid);
+extern time_t readdservbyname (struct database_dyn *db, struct hashentry *he,
+ struct datahead *dh);
+extern void addservbyport (struct database_dyn *db, int fd,
+ request_header *req, void *key, uid_t uid);
+extern time_t readdservbyport (struct database_dyn *db, struct hashentry *he,
+ struct datahead *dh);
+
+/* netgroupcache.c */
+extern void addinnetgr (struct database_dyn *db, int fd, request_header *req,
+ void *key, uid_t uid);
+extern time_t readdinnetgr (struct database_dyn *db, struct hashentry *he,
+ struct datahead *dh);
+extern void addgetnetgrent (struct database_dyn *db, int fd,
+ request_header *req, void *key, uid_t uid);
+extern time_t readdgetnetgrent (struct database_dyn *db, struct hashentry *he,
+ struct datahead *dh);
+
+/* mem.c */
+extern void *mempool_alloc (struct database_dyn *db, size_t len,
+ int data_alloc);
+extern void gc (struct database_dyn *db);
+
+
+/* nscd_setup_thread.c */
+extern int setup_thread (struct database_dyn *db);
+
+
+/* Special version of TEMP_FAILURE_RETRY for functions returning error
+ values. */
+#define TEMP_FAILURE_RETRY_VAL(expression) \
+ (__extension__ \
+ ({ long int __result; \
+ do __result = (long int) (expression); \
+ while (__result == EINTR); \
+ __result; }))
+
+#endif /* nscd.h */
diff --git a/REORG.TODO/nscd/nscd.init b/REORG.TODO/nscd/nscd.init
new file mode 100644
index 0000000000..a882da7d8b
--- /dev/null
+++ b/REORG.TODO/nscd/nscd.init
@@ -0,0 +1,116 @@
+#!/bin/bash
+#
+# nscd: Starts the Name Switch Cache Daemon
+#
+# chkconfig: - 30 74
+# description: This is a daemon which handles passwd and group lookups \
+# for running programs and cache the results for the next \
+# query. You should start this daemon if you use \
+# slow naming services like NIS, NIS+, LDAP, or hesiod.
+# processname: /usr/sbin/nscd
+# config: /etc/nscd.conf
+#
+### BEGIN INIT INFO
+# Provides: nscd
+# Required-Start: $syslog
+# Default-Stop: 0 1 6
+# Short-Description: Starts the Name Switch Cache Daemon
+# Description: This is a daemon which handles passwd and group lookups \
+# for running programs and cache the results for the next \
+# query. You should start this daemon if you use \
+# slow naming services like NIS, NIS+, LDAP, or hesiod.
+### END INIT INFO
+
+# Sanity checks.
+[ -f /etc/nscd.conf ] || exit 0
+[ -x /usr/sbin/nscd ] || exit 0
+
+# Source function library.
+. /etc/init.d/functions
+
+# nscd does not run on any kernel lower than 2.2.0 because of threading
+# problems, so we require that in first place.
+case $(uname -r) in
+ 2.[2-9].*)
+ # this is okay
+ ;;
+ [3-9]*)
+ # these are of course also okay
+ ;;
+ *)
+ #this is not
+ exit 1
+ ;;
+esac
+
+RETVAL=0
+prog=nscd
+
+start () {
+ [ -d /var/run/nscd ] || mkdir /var/run/nscd
+ [ -d /var/db/nscd ] || mkdir /var/db/nscd
+ echo -n $"Starting $prog: "
+ daemon /usr/sbin/nscd
+ RETVAL=$?
+ echo
+ [ $RETVAL -eq 0 ] && touch /var/lock/subsys/nscd
+ return $RETVAL
+}
+
+stop () {
+ echo -n $"Stopping $prog: "
+ /usr/sbin/nscd -K
+ RETVAL=$?
+ if [ $RETVAL -eq 0 ]; then
+ rm -f /var/lock/subsys/nscd
+ # nscd won't be able to remove these if it is running as
+ # a non-privileged user
+ rm -f /var/run/nscd/nscd.pid
+ rm -f /var/run/nscd/socket
+ success $"$prog shutdown"
+ else
+ failure $"$prog shutdown"
+ fi
+ echo
+ return $RETVAL
+}
+
+restart() {
+ stop
+ start
+}
+
+# See how we were called.
+case "$1" in
+ start)
+ start
+ RETVAL=$?
+ ;;
+ stop)
+ stop
+ RETVAL=$?
+ ;;
+ status)
+ status nscd
+ RETVAL=$?
+ ;;
+ restart)
+ restart
+ RETVAL=$?
+ ;;
+ try-restart | condrestart)
+ [ -e /var/lock/subsys/nscd ] && restart
+ RETVAL=$?
+ ;;
+ force-reload | reload)
+ echo -n $"Reloading $prog: "
+ killproc /usr/sbin/nscd -HUP
+ RETVAL=$?
+ echo
+ ;;
+ *)
+ echo $"Usage: $0 {start|stop|status|restart|reload|condrestart}"
+ RETVAL=1
+ ;;
+esac
+exit $RETVAL
diff --git a/REORG.TODO/nscd/nscd.service b/REORG.TODO/nscd/nscd.service
new file mode 100644
index 0000000000..ab38e8f982
--- /dev/null
+++ b/REORG.TODO/nscd/nscd.service
@@ -0,0 +1,19 @@
+# systemd service file for nscd
+
+[Unit]
+Description=Name Service Cache Daemon
+
+[Service]
+Type=forking
+ExecStart=/usr/sbin/nscd
+ExecStop=/usr/sbin/nscd --shutdown
+ExecReload=/usr/sbin/nscd -i passwd
+ExecReload=/usr/sbin/nscd -i group
+ExecReload=/usr/sbin/nscd -i hosts
+ExecReload=/usr/sbin/nscd -i services
+ExecReload=/usr/sbin/nscd -i netgroup
+Restart=always
+PIDFile=/run/nscd/nscd.pid
+
+[Install]
+WantedBy=multi-user.target
diff --git a/REORG.TODO/nscd/nscd.tmpfiles b/REORG.TODO/nscd/nscd.tmpfiles
new file mode 100644
index 0000000000..52edbba673
--- /dev/null
+++ b/REORG.TODO/nscd/nscd.tmpfiles
@@ -0,0 +1,4 @@
+# Configuration to create /run/nscd directory
+# Used as part of systemd's tmpfiles
+
+d /run/nscd 0755 root root
diff --git a/REORG.TODO/nscd/nscd_conf.c b/REORG.TODO/nscd/nscd_conf.c
new file mode 100644
index 0000000000..9c301ad73d
--- /dev/null
+++ b/REORG.TODO/nscd/nscd_conf.c
@@ -0,0 +1,315 @@
+/* Copyright (c) 1998-2017 Free Software Foundation, Inc.
+ This file is part of the GNU C Library.
+ Contributed by Thorsten Kukuk <kukuk@suse.de>, 1998.
+
+ 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 <ctype.h>
+#include <errno.h>
+#include <error.h>
+#include <libintl.h>
+#include <malloc.h>
+#include <pwd.h>
+#include <stdio.h>
+#include <stdio_ext.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/param.h>
+#include <sys/types.h>
+
+#include "dbg_log.h"
+#include "nscd.h"
+
+
+/* Names of the databases. */
+const char *const dbnames[lastdb] =
+{
+ [pwddb] = "passwd",
+ [grpdb] = "group",
+ [hstdb] = "hosts",
+ [servdb] = "services",
+ [netgrdb] = "netgroup"
+};
+
+
+static int
+find_db (const char *name)
+{
+ for (int cnt = 0; cnt < lastdb; ++cnt)
+ if (strcmp (name, dbnames[cnt]) == 0)
+ return cnt;
+
+ error (0, 0, _("database %s is not supported"), name);
+ return -1;
+}
+
+int
+nscd_parse_file (const char *fname, struct database_dyn dbs[lastdb])
+{
+ FILE *fp;
+ char *line, *cp, *entry, *arg1, *arg2;
+ size_t len;
+ int cnt;
+ const unsigned int initial_error_message_count = error_message_count;
+
+ /* Open the configuration file. */
+ fp = fopen (fname, "r");
+ if (fp == NULL)
+ return -1;
+
+ /* The stream is not used by more than one thread. */
+ (void) __fsetlocking (fp, FSETLOCKING_BYCALLER);
+
+ line = NULL;
+ len = 0;
+
+ do
+ {
+ ssize_t n = getline (&line, &len, fp);
+ if (n < 0)
+ break;
+ if (line[n - 1] == '\n')
+ line[n - 1] = '\0';
+
+ /* Because the file format does not know any form of quoting we
+ can search forward for the next '#' character and if found
+ make it terminating the line. */
+ *strchrnul (line, '#') = '\0';
+
+ /* If the line is blank it is ignored. */
+ if (line[0] == '\0')
+ continue;
+
+ entry = line;
+ while (isspace (*entry) && *entry != '\0')
+ ++entry;
+ cp = entry;
+ while (!isspace (*cp) && *cp != '\0')
+ ++cp;
+ arg1 = cp;
+ ++arg1;
+ *cp = '\0';
+ if (strlen (entry) == 0)
+ error (0, 0, _("Parse error: %s"), line);
+ while (isspace (*arg1) && *arg1 != '\0')
+ ++arg1;
+ cp = arg1;
+ while (!isspace (*cp) && *cp != '\0')
+ ++cp;
+ arg2 = cp;
+ ++arg2;
+ *cp = '\0';
+ if (strlen (arg2) > 0)
+ {
+ while (isspace (*arg2) && *arg2 != '\0')
+ ++arg2;
+ cp = arg2;
+ while (!isspace (*cp) && *cp != '\0')
+ ++cp;
+ *cp = '\0';
+ }
+
+ if (strcmp (entry, "positive-time-to-live") == 0)
+ {
+ int idx = find_db (arg1);
+ if (idx >= 0)
+ dbs[idx].postimeout = atol (arg2);
+ }
+ else if (strcmp (entry, "negative-time-to-live") == 0)
+ {
+ int idx = find_db (arg1);
+ if (idx >= 0)
+ dbs[idx].negtimeout = atol (arg2);
+ }
+ else if (strcmp (entry, "suggested-size") == 0)
+ {
+ int idx = find_db (arg1);
+ if (idx >= 0)
+ dbs[idx].suggested_module
+ = atol (arg2) ?: DEFAULT_SUGGESTED_MODULE;
+ }
+ else if (strcmp (entry, "enable-cache") == 0)
+ {
+ int idx = find_db (arg1);
+ if (idx >= 0)
+ {
+ if (strcmp (arg2, "no") == 0)
+ dbs[idx].enabled = 0;
+ else if (strcmp (arg2, "yes") == 0)
+ dbs[idx].enabled = 1;
+ }
+ }
+ else if (strcmp (entry, "check-files") == 0)
+ {
+ int idx = find_db (arg1);
+ if (idx >= 0)
+ {
+ if (strcmp (arg2, "no") == 0)
+ dbs[idx].check_file = 0;
+ else if (strcmp (arg2, "yes") == 0)
+ dbs[idx].check_file = 1;
+ }
+ }
+ else if (strcmp (entry, "max-db-size") == 0)
+ {
+ int idx = find_db (arg1);
+ if (idx >= 0)
+ dbs[idx].max_db_size = atol (arg2) ?: DEFAULT_MAX_DB_SIZE;
+ }
+ else if (strcmp (entry, "logfile") == 0)
+ set_logfile (arg1);
+ else if (strcmp (entry, "debug-level") == 0)
+ {
+ int level = atoi (arg1);
+ if (level > 0)
+ debug_level = level;
+ }
+ else if (strcmp (entry, "threads") == 0)
+ {
+ if (nthreads == -1)
+ nthreads = MAX (atol (arg1), lastdb);
+ }
+ else if (strcmp (entry, "max-threads") == 0)
+ {
+ max_nthreads = MAX (atol (arg1), lastdb);
+ }
+ else if (strcmp (entry, "server-user") == 0)
+ {
+ if (!arg1)
+ error (0, 0, _("Must specify user name for server-user option"));
+ else
+ server_user = xstrdup (arg1);
+ }
+ else if (strcmp (entry, "stat-user") == 0)
+ {
+ if (arg1 == NULL)
+ error (0, 0, _("Must specify user name for stat-user option"));
+ else
+ {
+ stat_user = xstrdup (arg1);
+
+ struct passwd *pw = getpwnam (stat_user);
+ if (pw != NULL)
+ stat_uid = pw->pw_uid;
+ }
+ }
+ else if (strcmp (entry, "persistent") == 0)
+ {
+ int idx = find_db (arg1);
+ if (idx >= 0)
+ {
+ if (strcmp (arg2, "no") == 0)
+ dbs[idx].persistent = 0;
+ else if (strcmp (arg2, "yes") == 0)
+ dbs[idx].persistent = 1;
+ }
+ }
+ else if (strcmp (entry, "shared") == 0)
+ {
+ int idx = find_db (arg1);
+ if (idx >= 0)
+ {
+ if (strcmp (arg2, "no") == 0)
+ dbs[idx].shared = 0;
+ else if (strcmp (arg2, "yes") == 0)
+ dbs[idx].shared = 1;
+ }
+ }
+ else if (strcmp (entry, "reload-count") == 0)
+ {
+ if (strcasecmp (arg1, "unlimited") == 0)
+ reload_count = UINT_MAX;
+ else
+ {
+ unsigned long int count = strtoul (arg1, NULL, 0);
+ if (count > UINT8_MAX - 1)
+ reload_count = UINT_MAX;
+ else
+ reload_count = count;
+ }
+ }
+ else if (strcmp (entry, "paranoia") == 0)
+ {
+ if (strcmp (arg1, "no") == 0)
+ paranoia = 0;
+ else if (strcmp (arg1, "yes") == 0)
+ paranoia = 1;
+ }
+ else if (strcmp (entry, "restart-interval") == 0)
+ {
+ if (arg1 != NULL)
+ restart_interval = atol (arg1);
+ else
+ error (0, 0, _("Must specify value for restart-interval option"));
+ }
+ else if (strcmp (entry, "auto-propagate") == 0)
+ {
+ int idx = find_db (arg1);
+ if (idx >= 0)
+ {
+ if (strcmp (arg2, "no") == 0)
+ dbs[idx].propagate = 0;
+ else if (strcmp (arg2, "yes") == 0)
+ dbs[idx].propagate = 1;
+ }
+ }
+ else
+ error (0, 0, _("Unknown option: %s %s %s"), entry, arg1, arg2);
+ }
+ while (!feof_unlocked (fp));
+
+ if (paranoia)
+ {
+ restart_time = time (NULL) + restart_interval;
+
+ /* Save the old current workding directory if we are in paranoia
+ mode. We have to change back to it. */
+ oldcwd = get_current_dir_name ();
+ if (oldcwd == NULL)
+ {
+ error (0, 0, _("\
+cannot get current working directory: %s; disabling paranoia mode"),
+ strerror (errno));
+ paranoia = 0;
+ }
+ }
+
+ /* Enforce sanity. */
+ if (max_nthreads < nthreads)
+ max_nthreads = nthreads;
+
+ for (cnt = 0; cnt < lastdb; ++cnt)
+ {
+ size_t datasize = (sizeof (struct database_pers_head)
+ + roundup (dbs[cnt].suggested_module
+ * sizeof (ref_t), ALIGN)
+ + (dbs[cnt].suggested_module
+ * DEFAULT_DATASIZE_PER_BUCKET));
+ if (datasize > dbs[cnt].max_db_size)
+ {
+ error (0, 0, _("maximum file size for %s database too small"),
+ dbnames[cnt]);
+ dbs[cnt].max_db_size = datasize;
+ }
+
+ }
+
+ /* Free the buffer. */
+ free (line);
+ /* Close configuration file. */
+ fclose (fp);
+
+ return error_message_count != initial_error_message_count;
+}
diff --git a/REORG.TODO/nscd/nscd_getai.c b/REORG.TODO/nscd/nscd_getai.c
new file mode 100644
index 0000000000..daaf6d68b2
--- /dev/null
+++ b/REORG.TODO/nscd/nscd_getai.c
@@ -0,0 +1,216 @@
+/* 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.
+
+ The GNU C Library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ The GNU C Library 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
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with the GNU C Library; if not, see
+ <http://www.gnu.org/licenses/>. */
+
+#include <assert.h>
+#include <errno.h>
+#include <netdb.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <not-cancel.h>
+
+#include "nscd-client.h"
+#include "nscd_proto.h"
+
+
+/* Define in nscd_gethst_r.c. */
+extern int __nss_not_use_nscd_hosts;
+
+
+/* We use the mapping from nscd_gethst. */
+libc_locked_map_ptr (extern, __hst_map_handle) attribute_hidden;
+
+/* Defined in nscd_gethst_r.c. */
+extern int __nss_have_localdomain attribute_hidden;
+
+
+int
+__nscd_getai (const char *key, struct nscd_ai_result **result, int *h_errnop)
+{
+ if (__glibc_unlikely (__nss_have_localdomain >= 0))
+ {
+ if (__nss_have_localdomain == 0)
+ __nss_have_localdomain = getenv ("LOCALDOMAIN") != NULL ? 1 : -1;
+ if (__nss_have_localdomain > 0)
+ {
+ __nss_not_use_nscd_hosts = 1;
+ return -1;
+ }
+ }
+
+ size_t keylen = strlen (key) + 1;
+ int gc_cycle;
+ int nretries = 0;
+
+ /* If the mapping is available, try to search there instead of
+ communicating with the nscd. */
+ struct mapped_database *mapped;
+ mapped = __nscd_get_map_ref (GETFDHST, "hosts", &__hst_map_handle,
+ &gc_cycle);
+
+ retry:;
+ struct nscd_ai_result *resultbuf = NULL;
+ const char *recend = (const char *) ~UINTMAX_C (0);
+ char *respdata = NULL;
+ int retval = -1;
+ int sock = -1;
+ ai_response_header ai_resp;
+
+ if (mapped != NO_MAPPING)
+ {
+ struct datahead *found = __nscd_cache_search (GETAI, key, keylen,
+ mapped, sizeof ai_resp);
+ if (found != NULL)
+ {
+ respdata = (char *) (&found->data[0].aidata + 1);
+ ai_resp = found->data[0].aidata;
+ recend = (const char *) found->data + found->recsize;
+ /* Now check if we can trust ai_resp fields. If GC is
+ in progress, it can contain anything. */
+ if (mapped->head->gc_cycle != gc_cycle)
+ {
+ retval = -2;
+ goto out;
+ }
+ }
+ }
+
+ /* If we do not have the cache mapped, try to get the data over the
+ socket. */
+ if (respdata == NULL)
+ {
+ sock = __nscd_open_socket (key, keylen, GETAI, &ai_resp,
+ sizeof (ai_resp));
+ if (sock == -1)
+ {
+ /* nscd not running or wrong version. */
+ __nss_not_use_nscd_hosts = 1;
+ goto out;
+ }
+ }
+
+ if (ai_resp.found == 1)
+ {
+ size_t datalen = ai_resp.naddrs + ai_resp.addrslen + ai_resp.canonlen;
+
+ /* This check really only affects the case where the data
+ comes from the mapped cache. */
+ if (respdata + datalen > recend)
+ {
+ assert (sock == -1);
+ goto out;
+ }
+
+ /* Create result. */
+ resultbuf = (struct nscd_ai_result *) malloc (sizeof (*resultbuf)
+ + datalen);
+ if (resultbuf == NULL)
+ {
+ *h_errnop = NETDB_INTERNAL;
+ goto out_close;
+ }
+
+ /* Set up the data structure, including pointers. */
+ resultbuf->naddrs = ai_resp.naddrs;
+ resultbuf->addrs = (char *) (resultbuf + 1);
+ resultbuf->family = (uint8_t *) (resultbuf->addrs + ai_resp.addrslen);
+ if (ai_resp.canonlen != 0)
+ resultbuf->canon = (char *) (resultbuf->family + resultbuf->naddrs);
+ else
+ resultbuf->canon = NULL;
+
+ if (respdata == NULL)
+ {
+ /* Read the data from the socket. */
+ if ((size_t) __readall (sock, resultbuf + 1, datalen) == datalen)
+ {
+ retval = 0;
+ *result = resultbuf;
+ }
+ else
+ {
+ free (resultbuf);
+ *h_errnop = NETDB_INTERNAL;
+ }
+ }
+ else
+ {
+ /* Copy the data in the block. */
+ memcpy (resultbuf + 1, respdata, datalen);
+
+ /* Try to detect corrupt databases. */
+ if (resultbuf->canon != NULL
+ && resultbuf->canon[ai_resp.canonlen - 1] != '\0')
+ /* We cannot use the database. */
+ {
+ if (mapped->head->gc_cycle != gc_cycle)
+ retval = -2;
+ else
+ free (resultbuf);
+ goto out_close;
+ }
+
+ retval = 0;
+ *result = resultbuf;
+ }
+ }
+ else
+ {
+ if (__glibc_unlikely (ai_resp.found == -1))
+ {
+ /* The daemon does not cache this database. */
+ __nss_not_use_nscd_hosts = 1;
+ goto out_close;
+ }
+
+ /* Store the error number. */
+ *h_errnop = ai_resp.error;
+
+ /* Set errno to 0 to indicate no error, just no found record. */
+ __set_errno (0);
+ /* Even though we have not found anything, the result is zero. */
+ retval = 0;
+ }
+
+ out_close:
+ if (sock != -1)
+ close_not_cancel_no_status (sock);
+ out:
+ if (__nscd_drop_map_ref (mapped, &gc_cycle) != 0)
+ {
+ /* When we come here this means there has been a GC cycle while we
+ were looking for the data. This means the data might have been
+ inconsistent. Retry if possible. */
+ if ((gc_cycle & 1) != 0 || ++nretries == 5 || retval == -1)
+ {
+ /* nscd is just running gc now. Disable using the mapping. */
+ if (atomic_decrement_val (&mapped->counter) == 0)
+ __nscd_unmap (mapped);
+ mapped = NO_MAPPING;
+ }
+
+ if (retval != -1)
+ {
+ *result = NULL;
+ free (resultbuf);
+ goto retry;
+ }
+ }
+
+ return retval;
+}
diff --git a/REORG.TODO/nscd/nscd_getgr_r.c b/REORG.TODO/nscd/nscd_getgr_r.c
new file mode 100644
index 0000000000..87b4552197
--- /dev/null
+++ b/REORG.TODO/nscd/nscd_getgr_r.c
@@ -0,0 +1,330 @@
+/* Copyright (C) 1998-2017 Free Software Foundation, Inc.
+ This file is part of the GNU C Library.
+ Contributed by Thorsten Kukuk <kukuk@uni-paderborn.de>, 1998.
+
+ The GNU C Library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ The GNU C Library 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
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with the GNU C Library; if not, see
+ <http://www.gnu.org/licenses/>. */
+
+#include <alloca.h>
+#include <assert.h>
+#include <errno.h>
+#include <grp.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/mman.h>
+#include <sys/socket.h>
+#include <sys/uio.h>
+#include <sys/un.h>
+#include <not-cancel.h>
+#include <_itoa.h>
+#include <scratch_buffer.h>
+
+#include "nscd-client.h"
+#include "nscd_proto.h"
+
+int __nss_not_use_nscd_group;
+
+static int nscd_getgr_r (const char *key, size_t keylen, request_type type,
+ struct group *resultbuf, char *buffer,
+ size_t buflen, struct group **result)
+ internal_function;
+
+
+int
+__nscd_getgrnam_r (const char *name, struct group *resultbuf, char *buffer,
+ size_t buflen, struct group **result)
+{
+ return nscd_getgr_r (name, strlen (name) + 1, GETGRBYNAME, resultbuf,
+ buffer, buflen, result);
+}
+
+
+int
+__nscd_getgrgid_r (gid_t gid, struct group *resultbuf, char *buffer,
+ size_t buflen, struct group **result)
+{
+ char buf[3 * sizeof (gid_t)];
+ buf[sizeof (buf) - 1] = '\0';
+ char *cp = _itoa_word (gid, buf + sizeof (buf) - 1, 10, 0);
+
+ return nscd_getgr_r (cp, buf + sizeof (buf) - cp, GETGRBYGID, resultbuf,
+ buffer, buflen, result);
+}
+
+
+libc_locked_map_ptr (,__gr_map_handle) attribute_hidden;
+/* Note that we only free the structure if necessary. The memory
+ mapping is not removed since it is not visible to the malloc
+ handling. */
+libc_freeres_fn (gr_map_free)
+{
+ if (__gr_map_handle.mapped != NO_MAPPING)
+ {
+ void *p = __gr_map_handle.mapped;
+ __gr_map_handle.mapped = NO_MAPPING;
+ free (p);
+ }
+}
+
+
+static int
+internal_function
+nscd_getgr_r (const char *key, size_t keylen, request_type type,
+ struct group *resultbuf, char *buffer, size_t buflen,
+ struct group **result)
+{
+ int gc_cycle;
+ int nretries = 0;
+ const uint32_t *len = NULL;
+ struct scratch_buffer lenbuf;
+ scratch_buffer_init (&lenbuf);
+
+ /* If the mapping is available, try to search there instead of
+ communicating with the nscd. */
+ struct mapped_database *mapped = __nscd_get_map_ref (GETFDGR, "group",
+ &__gr_map_handle,
+ &gc_cycle);
+ retry:;
+ const char *gr_name = NULL;
+ size_t gr_name_len = 0;
+ int retval = -1;
+ const char *recend = (const char *) ~UINTMAX_C (0);
+ gr_response_header gr_resp;
+
+ if (mapped != NO_MAPPING)
+ {
+ struct datahead *found = __nscd_cache_search (type, key, keylen, mapped,
+ sizeof gr_resp);
+ if (found != NULL)
+ {
+ len = (const uint32_t *) (&found->data[0].grdata + 1);
+ gr_resp = found->data[0].grdata;
+ gr_name = ((const char *) len
+ + gr_resp.gr_mem_cnt * sizeof (uint32_t));
+ gr_name_len = gr_resp.gr_name_len + gr_resp.gr_passwd_len;
+ recend = (const char *) found->data + found->recsize;
+ /* Now check if we can trust gr_resp fields. If GC is
+ in progress, it can contain anything. */
+ if (mapped->head->gc_cycle != gc_cycle)
+ {
+ retval = -2;
+ goto out;
+ }
+
+ /* The alignment is always sufficient, unless GC is in progress. */
+ assert (((uintptr_t) len & (__alignof__ (*len) - 1)) == 0);
+ }
+ }
+
+ int sock = -1;
+ if (gr_name == NULL)
+ {
+ sock = __nscd_open_socket (key, keylen, type, &gr_resp,
+ sizeof (gr_resp));
+ if (sock == -1)
+ {
+ __nss_not_use_nscd_group = 1;
+ goto out;
+ }
+ }
+
+ /* No value found so far. */
+ *result = NULL;
+
+ if (__glibc_unlikely (gr_resp.found == -1))
+ {
+ /* The daemon does not cache this database. */
+ __nss_not_use_nscd_group = 1;
+ goto out_close;
+ }
+
+ if (gr_resp.found == 1)
+ {
+ struct iovec vec[2];
+ char *p = buffer;
+ size_t total_len;
+ uintptr_t align;
+ nscd_ssize_t cnt;
+
+ /* Now allocate the buffer the array for the group members. We must
+ align the pointer. */
+ align = ((__alignof__ (char *) - (p - ((char *) 0)))
+ & (__alignof__ (char *) - 1));
+ total_len = (align + (1 + gr_resp.gr_mem_cnt) * sizeof (char *)
+ + gr_resp.gr_name_len + gr_resp.gr_passwd_len);
+ if (__glibc_unlikely (buflen < total_len))
+ {
+ no_room:
+ __set_errno (ERANGE);
+ retval = ERANGE;
+ goto out_close;
+ }
+ buflen -= total_len;
+
+ p += align;
+ resultbuf->gr_mem = (char **) p;
+ p += (1 + gr_resp.gr_mem_cnt) * sizeof (char *);
+
+ /* Set pointers for strings. */
+ resultbuf->gr_name = p;
+ p += gr_resp.gr_name_len;
+ resultbuf->gr_passwd = p;
+ p += gr_resp.gr_passwd_len;
+
+ /* Fill in what we know now. */
+ resultbuf->gr_gid = gr_resp.gr_gid;
+
+ /* Read the length information, group name, and password. */
+ if (gr_name == NULL)
+ {
+ /* Handle a simple, usual case: no group members. */
+ if (__glibc_likely (gr_resp.gr_mem_cnt == 0))
+ {
+ size_t n = gr_resp.gr_name_len + gr_resp.gr_passwd_len;
+ if (__builtin_expect (__readall (sock, resultbuf->gr_name, n)
+ != (ssize_t) n, 0))
+ goto out_close;
+ }
+ else
+ {
+ /* Allocate array to store lengths. */
+ if (!scratch_buffer_set_array_size
+ (&lenbuf, gr_resp.gr_mem_cnt, sizeof (uint32_t)))
+ goto out_close;
+ len = lenbuf.data;
+
+ vec[0].iov_base = (void *) len;
+ vec[0].iov_len = gr_resp.gr_mem_cnt * sizeof (uint32_t);
+ vec[1].iov_base = resultbuf->gr_name;
+ vec[1].iov_len = gr_resp.gr_name_len + gr_resp.gr_passwd_len;
+ total_len = vec[0].iov_len + vec[1].iov_len;
+
+ /* Get this data. */
+ size_t n = __readvall (sock, vec, 2);
+ if (__glibc_unlikely (n != total_len))
+ goto out_close;
+ }
+ }
+ else
+ /* We already have the data. Just copy the group name and
+ password. */
+ memcpy (resultbuf->gr_name, gr_name,
+ gr_resp.gr_name_len + gr_resp.gr_passwd_len);
+
+ /* Clear the terminating entry. */
+ resultbuf->gr_mem[gr_resp.gr_mem_cnt] = NULL;
+
+ /* Prepare reading the group members. */
+ total_len = 0;
+ for (cnt = 0; cnt < gr_resp.gr_mem_cnt; ++cnt)
+ {
+ resultbuf->gr_mem[cnt] = p;
+ total_len += len[cnt];
+ p += len[cnt];
+ }
+
+ if (__glibc_unlikely (gr_name + gr_name_len + total_len > recend))
+ {
+ /* len array might contain garbage during nscd GC cycle,
+ retry rather than fail in that case. */
+ if (gr_name != NULL && mapped->head->gc_cycle != gc_cycle)
+ retval = -2;
+ goto out_close;
+ }
+ if (__glibc_unlikely (total_len > buflen))
+ {
+ /* len array might contain garbage during nscd GC cycle,
+ retry rather than fail in that case. */
+ if (gr_name != NULL && mapped->head->gc_cycle != gc_cycle)
+ {
+ retval = -2;
+ goto out_close;
+ }
+ else
+ goto no_room;
+ }
+
+ retval = 0;
+
+ /* If there are no group members TOTAL_LEN is zero. */
+ if (gr_name == NULL)
+ {
+ if (total_len > 0
+ && __builtin_expect (__readall (sock, resultbuf->gr_mem[0],
+ total_len) != total_len, 0))
+ {
+ /* The `errno' to some value != ERANGE. */
+ __set_errno (ENOENT);
+ retval = ENOENT;
+ }
+ else
+ *result = resultbuf;
+ }
+ else
+ {
+ /* Copy the group member names. */
+ memcpy (resultbuf->gr_mem[0], gr_name + gr_name_len, total_len);
+
+ /* Try to detect corrupt databases. */
+ if (resultbuf->gr_name[gr_name_len - 1] != '\0'
+ || resultbuf->gr_passwd[gr_resp.gr_passwd_len - 1] != '\0'
+ || ({for (cnt = 0; cnt < gr_resp.gr_mem_cnt; ++cnt)
+ if (resultbuf->gr_mem[cnt][len[cnt] - 1] != '\0')
+ break;
+ cnt < gr_resp.gr_mem_cnt; }))
+ {
+ /* We cannot use the database. */
+ retval = mapped->head->gc_cycle != gc_cycle ? -2 : -1;
+ goto out_close;
+ }
+
+ *result = resultbuf;
+ }
+ }
+ else
+ {
+ /* Set errno to 0 to indicate no error, just no found record. */
+ __set_errno (0);
+ /* Even though we have not found anything, the result is zero. */
+ retval = 0;
+ }
+
+ out_close:
+ if (sock != -1)
+ close_not_cancel_no_status (sock);
+ out:
+ if (__nscd_drop_map_ref (mapped, &gc_cycle) != 0)
+ {
+ /* When we come here this means there has been a GC cycle while we
+ were looking for the data. This means the data might have been
+ inconsistent. Retry if possible. */
+ if ((gc_cycle & 1) != 0 || ++nretries == 5 || retval == -1)
+ {
+ /* nscd is just running gc now. Disable using the mapping. */
+ if (atomic_decrement_val (&mapped->counter) == 0)
+ __nscd_unmap (mapped);
+ mapped = NO_MAPPING;
+ }
+
+ if (retval != -1)
+ goto retry;
+ }
+
+ scratch_buffer_free (&lenbuf);
+
+ return retval;
+}
diff --git a/REORG.TODO/nscd/nscd_gethst_r.c b/REORG.TODO/nscd/nscd_gethst_r.c
new file mode 100644
index 0000000000..daa708b3d3
--- /dev/null
+++ b/REORG.TODO/nscd/nscd_gethst_r.c
@@ -0,0 +1,459 @@
+/* Copyright (C) 1998-2017 Free Software Foundation, Inc.
+ This file is part of the GNU C Library.
+ Contributed by Ulrich Drepper <drepper@cygnus.com>, 1998.
+
+ The GNU C Library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ The GNU C Library 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
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with the GNU C Library; if not, see
+ <http://www.gnu.org/licenses/>. */
+
+#include <errno.h>
+#include <resolv/resolv-internal.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdint.h>
+#include <arpa/nameser.h>
+#include <not-cancel.h>
+
+#include "nscd-client.h"
+#include "nscd_proto.h"
+
+int __nss_not_use_nscd_hosts;
+
+static int nscd_gethst_r (const char *key, size_t keylen, request_type type,
+ struct hostent *resultbuf, char *buffer,
+ size_t buflen, struct hostent **result,
+ int *h_errnop) internal_function;
+
+
+int
+__nscd_gethostbyname_r (const char *name, struct hostent *resultbuf,
+ char *buffer, size_t buflen, struct hostent **result,
+ int *h_errnop)
+{
+ request_type reqtype;
+
+ reqtype = res_use_inet6 () ? GETHOSTBYNAMEv6 : GETHOSTBYNAME;
+
+ return nscd_gethst_r (name, strlen (name) + 1, reqtype, resultbuf,
+ buffer, buflen, result, h_errnop);
+}
+
+
+int
+__nscd_gethostbyname2_r (const char *name, int af, struct hostent *resultbuf,
+ char *buffer, size_t buflen, struct hostent **result,
+ int *h_errnop)
+{
+ request_type reqtype;
+
+ reqtype = af == AF_INET6 ? GETHOSTBYNAMEv6 : GETHOSTBYNAME;
+
+ return nscd_gethst_r (name, strlen (name) + 1, reqtype, resultbuf,
+ buffer, buflen, result, h_errnop);
+}
+
+
+int
+__nscd_gethostbyaddr_r (const void *addr, socklen_t len, int type,
+ struct hostent *resultbuf, char *buffer, size_t buflen,
+ struct hostent **result, int *h_errnop)
+{
+ request_type reqtype;
+
+ if (!((len == INADDRSZ && type == AF_INET)
+ || (len == IN6ADDRSZ && type == AF_INET6)))
+ /* LEN and TYPE do not match. */
+ return -1;
+
+ reqtype = type == AF_INET6 ? GETHOSTBYADDRv6 : GETHOSTBYADDR;
+
+ return nscd_gethst_r (addr, len, reqtype, resultbuf, buffer, buflen, result,
+ h_errnop);
+}
+
+
+libc_locked_map_ptr (, __hst_map_handle) attribute_hidden;
+/* Note that we only free the structure if necessary. The memory
+ mapping is not removed since it is not visible to the malloc
+ handling. */
+libc_freeres_fn (hst_map_free)
+{
+ if (__hst_map_handle.mapped != NO_MAPPING)
+ {
+ void *p = __hst_map_handle.mapped;
+ __hst_map_handle.mapped = NO_MAPPING;
+ free (p);
+ }
+}
+
+
+uint32_t
+__nscd_get_nl_timestamp (void)
+{
+ uint32_t retval;
+ if (__nss_not_use_nscd_hosts != 0)
+ return 0;
+
+ /* __nscd_get_mapping can change hst_map_handle.mapped to NO_MAPPING.
+ However, __nscd_get_mapping assumes the prior value was not NO_MAPPING.
+ Thus we have to acquire the lock to prevent this thread from changing
+ hst_map_handle.mapped to NO_MAPPING while another thread is inside
+ __nscd_get_mapping. */
+ if (!__nscd_acquire_maplock (&__hst_map_handle))
+ return 0;
+
+ struct mapped_database *map = __hst_map_handle.mapped;
+
+ if (map == NULL
+ || (map != NO_MAPPING
+ && map->head->nscd_certainly_running == 0
+ && map->head->timestamp + MAPPING_TIMEOUT < time (NULL)))
+ map = __nscd_get_mapping (GETFDHST, "hosts", &__hst_map_handle.mapped);
+
+ if (map == NO_MAPPING)
+ retval = 0;
+ else
+ retval = map->head->extra_data[NSCD_HST_IDX_CONF_TIMESTAMP];
+
+ /* Release the lock. */
+ __hst_map_handle.lock = 0;
+
+ return retval;
+}
+
+
+int __nss_have_localdomain attribute_hidden;
+
+static int
+internal_function
+nscd_gethst_r (const char *key, size_t keylen, request_type type,
+ struct hostent *resultbuf, char *buffer, size_t buflen,
+ struct hostent **result, int *h_errnop)
+{
+ if (__glibc_unlikely (__nss_have_localdomain >= 0))
+ {
+ if (__nss_have_localdomain == 0)
+ __nss_have_localdomain = getenv ("LOCALDOMAIN") != NULL ? 1 : -1;
+ if (__nss_have_localdomain > 0)
+ {
+ __nss_not_use_nscd_hosts = 1;
+ return -1;
+ }
+ }
+
+ int gc_cycle;
+ int nretries = 0;
+
+ /* If the mapping is available, try to search there instead of
+ communicating with the nscd. */
+ struct mapped_database *mapped;
+ mapped = __nscd_get_map_ref (GETFDHST, "hosts", &__hst_map_handle,
+ &gc_cycle);
+
+ retry:;
+ const char *h_name = NULL;
+ const uint32_t *aliases_len = NULL;
+ const char *addr_list = NULL;
+ size_t addr_list_len = 0;
+ int retval = -1;
+ const char *recend = (const char *) ~UINTMAX_C (0);
+ int sock = -1;
+ hst_response_header hst_resp;
+ if (mapped != NO_MAPPING)
+ {
+ /* No const qualifier, as it can change during garbage collection. */
+ struct datahead *found = __nscd_cache_search (type, key, keylen, mapped,
+ sizeof hst_resp);
+ if (found != NULL)
+ {
+ h_name = (char *) (&found->data[0].hstdata + 1);
+ hst_resp = found->data[0].hstdata;
+ aliases_len = (uint32_t *) (h_name + hst_resp.h_name_len);
+ addr_list = ((char *) aliases_len
+ + hst_resp.h_aliases_cnt * sizeof (uint32_t));
+ addr_list_len = hst_resp.h_addr_list_cnt * INADDRSZ;
+ recend = (const char *) found->data + found->recsize;
+ /* Now check if we can trust hst_resp fields. If GC is
+ in progress, it can contain anything. */
+ if (mapped->head->gc_cycle != gc_cycle)
+ {
+ retval = -2;
+ goto out;
+ }
+
+#if !_STRING_ARCH_unaligned
+ /* The aliases_len array in the mapped database might very
+ well be unaligned. We will access it word-wise so on
+ platforms which do not tolerate unaligned accesses we
+ need to make an aligned copy. */
+ if (((uintptr_t) aliases_len & (__alignof__ (*aliases_len) - 1))
+ != 0)
+ {
+ uint32_t *tmp = alloca (hst_resp.h_aliases_cnt
+ * sizeof (uint32_t));
+ aliases_len = memcpy (tmp, aliases_len,
+ hst_resp.h_aliases_cnt
+ * sizeof (uint32_t));
+ }
+#endif
+ if (type != GETHOSTBYADDR && type != GETHOSTBYNAME)
+ {
+ if (hst_resp.h_length == INADDRSZ)
+ addr_list += addr_list_len;
+ addr_list_len = hst_resp.h_addr_list_cnt * IN6ADDRSZ;
+ }
+ if (__builtin_expect ((const char *) addr_list + addr_list_len
+ > recend, 0))
+ goto out;
+ }
+ }
+
+ if (h_name == NULL)
+ {
+ sock = __nscd_open_socket (key, keylen, type, &hst_resp,
+ sizeof (hst_resp));
+ if (sock == -1)
+ {
+ __nss_not_use_nscd_hosts = 1;
+ goto out;
+ }
+ }
+
+ /* No value found so far. */
+ *result = NULL;
+
+ if (__glibc_unlikely (hst_resp.found == -1))
+ {
+ /* The daemon does not cache this database. */
+ __nss_not_use_nscd_hosts = 1;
+ goto out_close;
+ }
+
+ if (hst_resp.found == 1)
+ {
+ char *cp = buffer;
+ uintptr_t align1;
+ uintptr_t align2;
+ size_t total_len;
+ ssize_t cnt;
+ char *ignore;
+ int n;
+
+ /* A first check whether the buffer is sufficiently large is possible. */
+ /* Now allocate the buffer the array for the group members. We must
+ align the pointer and the base of the h_addr_list pointers. */
+ align1 = ((__alignof__ (char *) - (cp - ((char *) 0)))
+ & (__alignof__ (char *) - 1));
+ align2 = ((__alignof__ (char *) - ((cp + align1 + hst_resp.h_name_len)
+ - ((char *) 0)))
+ & (__alignof__ (char *) - 1));
+ if (buflen < (align1 + hst_resp.h_name_len + align2
+ + ((hst_resp.h_aliases_cnt + hst_resp.h_addr_list_cnt
+ + 2)
+ * sizeof (char *))
+ + hst_resp.h_addr_list_cnt * (type == AF_INET
+ ? INADDRSZ : IN6ADDRSZ)))
+ {
+ no_room:
+ *h_errnop = NETDB_INTERNAL;
+ __set_errno (ERANGE);
+ retval = ERANGE;
+ goto out_close;
+ }
+ cp += align1;
+
+ /* Prepare the result as far as we can. */
+ resultbuf->h_aliases = (char **) cp;
+ cp += (hst_resp.h_aliases_cnt + 1) * sizeof (char *);
+ resultbuf->h_addr_list = (char **) cp;
+ cp += (hst_resp.h_addr_list_cnt + 1) * sizeof (char *);
+
+ resultbuf->h_name = cp;
+ cp += hst_resp.h_name_len + align2;
+
+ if (type == GETHOSTBYADDR || type == GETHOSTBYNAME)
+ {
+ resultbuf->h_addrtype = AF_INET;
+ resultbuf->h_length = INADDRSZ;
+ }
+ else
+ {
+ resultbuf->h_addrtype = AF_INET6;
+ resultbuf->h_length = IN6ADDRSZ;
+ }
+ for (cnt = 0; cnt < hst_resp.h_addr_list_cnt; ++cnt)
+ {
+ resultbuf->h_addr_list[cnt] = cp;
+ cp += resultbuf->h_length;
+ }
+ resultbuf->h_addr_list[cnt] = NULL;
+
+ if (h_name == NULL)
+ {
+ struct iovec vec[4];
+
+ vec[0].iov_base = resultbuf->h_name;
+ vec[0].iov_len = hst_resp.h_name_len;
+ total_len = hst_resp.h_name_len;
+ n = 1;
+
+ if (hst_resp.h_aliases_cnt > 0)
+ {
+ aliases_len = alloca (hst_resp.h_aliases_cnt
+ * sizeof (uint32_t));
+ vec[n].iov_base = (void *) aliases_len;
+ vec[n].iov_len = hst_resp.h_aliases_cnt * sizeof (uint32_t);
+
+ total_len += hst_resp.h_aliases_cnt * sizeof (uint32_t);
+ ++n;
+ }
+
+ if (type == GETHOSTBYADDR || type == GETHOSTBYNAME)
+ {
+ vec[n].iov_base = resultbuf->h_addr_list[0];
+ vec[n].iov_len = hst_resp.h_addr_list_cnt * INADDRSZ;
+
+ total_len += hst_resp.h_addr_list_cnt * INADDRSZ;
+
+ ++n;
+ }
+ else
+ {
+ if (hst_resp.h_length == INADDRSZ)
+ {
+ ignore = alloca (hst_resp.h_addr_list_cnt * INADDRSZ);
+ vec[n].iov_base = ignore;
+ vec[n].iov_len = hst_resp.h_addr_list_cnt * INADDRSZ;
+
+ total_len += hst_resp.h_addr_list_cnt * INADDRSZ;
+
+ ++n;
+ }
+
+ vec[n].iov_base = resultbuf->h_addr_list[0];
+ vec[n].iov_len = hst_resp.h_addr_list_cnt * IN6ADDRSZ;
+
+ total_len += hst_resp.h_addr_list_cnt * IN6ADDRSZ;
+
+ ++n;
+ }
+
+ if ((size_t) __readvall (sock, vec, n) != total_len)
+ goto out_close;
+ }
+ else
+ {
+ memcpy (resultbuf->h_name, h_name, hst_resp.h_name_len);
+ memcpy (resultbuf->h_addr_list[0], addr_list, addr_list_len);
+ }
+
+ /* Now we also can read the aliases. */
+ total_len = 0;
+ for (cnt = 0; cnt < hst_resp.h_aliases_cnt; ++cnt)
+ {
+ resultbuf->h_aliases[cnt] = cp;
+ cp += aliases_len[cnt];
+ total_len += aliases_len[cnt];
+ }
+ resultbuf->h_aliases[cnt] = NULL;
+
+ if (__builtin_expect ((const char *) addr_list + addr_list_len
+ + total_len > recend, 0))
+ {
+ /* aliases_len array might contain garbage during nscd GC cycle,
+ retry rather than fail in that case. */
+ if (addr_list != NULL && mapped->head->gc_cycle != gc_cycle)
+ retval = -2;
+ goto out_close;
+ }
+ /* See whether this would exceed the buffer capacity. */
+ if (__glibc_unlikely (cp > buffer + buflen))
+ {
+ /* aliases_len array might contain garbage during nscd GC cycle,
+ retry rather than fail in that case. */
+ if (addr_list != NULL && mapped->head->gc_cycle != gc_cycle)
+ {
+ retval = -2;
+ goto out_close;
+ }
+ goto no_room;
+ }
+
+ /* And finally read the aliases. */
+ if (addr_list == NULL)
+ {
+ if (total_len == 0
+ || ((size_t) __readall (sock, resultbuf->h_aliases[0], total_len)
+ == total_len))
+ {
+ retval = 0;
+ *result = resultbuf;
+ }
+ }
+ else
+ {
+ memcpy (resultbuf->h_aliases[0],
+ (const char *) addr_list + addr_list_len, total_len);
+
+ /* Try to detect corrupt databases. */
+ if (resultbuf->h_name[hst_resp.h_name_len - 1] != '\0'
+ || ({for (cnt = 0; cnt < hst_resp.h_aliases_cnt; ++cnt)
+ if (resultbuf->h_aliases[cnt][aliases_len[cnt] - 1]
+ != '\0')
+ break;
+ cnt < hst_resp.h_aliases_cnt; }))
+ {
+ /* We cannot use the database. */
+ if (mapped->head->gc_cycle != gc_cycle)
+ retval = -2;
+ goto out_close;
+ }
+
+ retval = 0;
+ *result = resultbuf;
+ }
+ }
+ else
+ {
+ /* Store the error number. */
+ *h_errnop = hst_resp.error;
+
+ /* Set errno to 0 to indicate no error, just no found record. */
+ __set_errno (0);
+ /* Even though we have not found anything, the result is zero. */
+ retval = 0;
+ }
+
+ out_close:
+ if (sock != -1)
+ close_not_cancel_no_status (sock);
+ out:
+ if (__nscd_drop_map_ref (mapped, &gc_cycle) != 0)
+ {
+ /* When we come here this means there has been a GC cycle while we
+ were looking for the data. This means the data might have been
+ inconsistent. Retry if possible. */
+ if ((gc_cycle & 1) != 0 || ++nretries == 5 || retval == -1)
+ {
+ /* nscd is just running gc now. Disable using the mapping. */
+ if (atomic_decrement_val (&mapped->counter) == 0)
+ __nscd_unmap (mapped);
+ mapped = NO_MAPPING;
+ }
+
+ if (retval != -1)
+ goto retry;
+ }
+
+ return retval;
+}
diff --git a/REORG.TODO/nscd/nscd_getpw_r.c b/REORG.TODO/nscd/nscd_getpw_r.c
new file mode 100644
index 0000000000..b291d2fa44
--- /dev/null
+++ b/REORG.TODO/nscd/nscd_getpw_r.c
@@ -0,0 +1,241 @@
+/* Copyright (C) 1998-2017 Free Software Foundation, Inc.
+ This file is part of the GNU C Library.
+ Contributed by Thorsten Kukuk <kukuk@uni-paderborn.de>, 1998.
+
+ The GNU C Library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ The GNU C Library 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
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with the GNU C Library; if not, see
+ <http://www.gnu.org/licenses/>. */
+
+#include <assert.h>
+#include <errno.h>
+#include <pwd.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/mman.h>
+#include <sys/socket.h>
+#include <sys/uio.h>
+#include <sys/un.h>
+#include <not-cancel.h>
+#include <_itoa.h>
+
+#include "nscd-client.h"
+#include "nscd_proto.h"
+
+int __nss_not_use_nscd_passwd;
+
+static int nscd_getpw_r (const char *key, size_t keylen, request_type type,
+ struct passwd *resultbuf, char *buffer,
+ size_t buflen, struct passwd **result)
+ internal_function;
+
+int
+__nscd_getpwnam_r (const char *name, struct passwd *resultbuf, char *buffer,
+ size_t buflen, struct passwd **result)
+{
+ if (name == NULL)
+ return -1;
+
+ return nscd_getpw_r (name, strlen (name) + 1, GETPWBYNAME, resultbuf,
+ buffer, buflen, result);
+}
+
+int
+__nscd_getpwuid_r (uid_t uid, struct passwd *resultbuf, char *buffer,
+ size_t buflen, struct passwd **result)
+{
+ char buf[3 * sizeof (uid_t)];
+ buf[sizeof (buf) - 1] = '\0';
+ char *cp = _itoa_word (uid, buf + sizeof (buf) - 1, 10, 0);
+
+ return nscd_getpw_r (cp, buf + sizeof (buf) - cp, GETPWBYUID, resultbuf,
+ buffer, buflen, result);
+}
+
+
+libc_locked_map_ptr (static, map_handle);
+/* Note that we only free the structure if necessary. The memory
+ mapping is not removed since it is not visible to the malloc
+ handling. */
+libc_freeres_fn (pw_map_free)
+{
+ if (map_handle.mapped != NO_MAPPING)
+ {
+ void *p = map_handle.mapped;
+ map_handle.mapped = NO_MAPPING;
+ free (p);
+ }
+}
+
+
+static int
+internal_function
+nscd_getpw_r (const char *key, size_t keylen, request_type type,
+ struct passwd *resultbuf, char *buffer, size_t buflen,
+ struct passwd **result)
+{
+ int gc_cycle;
+ int nretries = 0;
+
+ /* If the mapping is available, try to search there instead of
+ communicating with the nscd. */
+ struct mapped_database *mapped;
+ mapped = __nscd_get_map_ref (GETFDPW, "passwd", &map_handle, &gc_cycle);
+
+ retry:;
+ const char *pw_name = NULL;
+ int retval = -1;
+ const char *recend = (const char *) ~UINTMAX_C (0);
+ pw_response_header pw_resp;
+
+ if (mapped != NO_MAPPING)
+ {
+ struct datahead *found = __nscd_cache_search (type, key, keylen, mapped,
+ sizeof pw_resp);
+ if (found != NULL)
+ {
+ pw_name = (const char *) (&found->data[0].pwdata + 1);
+ pw_resp = found->data[0].pwdata;
+ recend = (const char *) found->data + found->recsize;
+ /* Now check if we can trust pw_resp fields. If GC is
+ in progress, it can contain anything. */
+ if (mapped->head->gc_cycle != gc_cycle)
+ {
+ retval = -2;
+ goto out;
+ }
+ }
+ }
+
+ int sock = -1;
+ if (pw_name == NULL)
+ {
+ sock = __nscd_open_socket (key, keylen, type, &pw_resp,
+ sizeof (pw_resp));
+ if (sock == -1)
+ {
+ __nss_not_use_nscd_passwd = 1;
+ goto out;
+ }
+ }
+
+ /* No value found so far. */
+ *result = NULL;
+
+ if (__glibc_unlikely (pw_resp.found == -1))
+ {
+ /* The daemon does not cache this database. */
+ __nss_not_use_nscd_passwd = 1;
+ goto out_close;
+ }
+
+ if (pw_resp.found == 1)
+ {
+ /* Set the information we already have. */
+ resultbuf->pw_uid = pw_resp.pw_uid;
+ resultbuf->pw_gid = pw_resp.pw_gid;
+
+ char *p = buffer;
+ /* get pw_name */
+ resultbuf->pw_name = p;
+ p += pw_resp.pw_name_len;
+ /* get pw_passwd */
+ resultbuf->pw_passwd = p;
+ p += pw_resp.pw_passwd_len;
+ /* get pw_gecos */
+ resultbuf->pw_gecos = p;
+ p += pw_resp.pw_gecos_len;
+ /* get pw_dir */
+ resultbuf->pw_dir = p;
+ p += pw_resp.pw_dir_len;
+ /* get pw_pshell */
+ resultbuf->pw_shell = p;
+ p += pw_resp.pw_shell_len;
+
+ ssize_t total = p - buffer;
+ if (__glibc_unlikely (pw_name + total > recend))
+ goto out_close;
+ if (__glibc_unlikely (buflen < total))
+ {
+ __set_errno (ERANGE);
+ retval = ERANGE;
+ goto out_close;
+ }
+
+ retval = 0;
+ if (pw_name == NULL)
+ {
+ ssize_t nbytes = __readall (sock, buffer, total);
+
+ if (__glibc_unlikely (nbytes != total))
+ {
+ /* The `errno' to some value != ERANGE. */
+ __set_errno (ENOENT);
+ retval = ENOENT;
+ }
+ else
+ *result = resultbuf;
+ }
+ else
+ {
+ /* Copy the various strings. */
+ memcpy (resultbuf->pw_name, pw_name, total);
+
+ /* Try to detect corrupt databases. */
+ if (resultbuf->pw_name[pw_resp.pw_name_len - 1] != '\0'
+ || resultbuf->pw_passwd[pw_resp.pw_passwd_len - 1] != '\0'
+ || resultbuf->pw_gecos[pw_resp.pw_gecos_len - 1] != '\0'
+ || resultbuf->pw_dir[pw_resp.pw_dir_len - 1] != '\0'
+ || resultbuf->pw_shell[pw_resp.pw_shell_len - 1] != '\0')
+ {
+ /* We cannot use the database. */
+ retval = mapped->head->gc_cycle != gc_cycle ? -2 : -1;
+ goto out_close;
+ }
+
+ *result = resultbuf;
+ }
+ }
+ else
+ {
+ /* Set errno to 0 to indicate no error, just no found record. */
+ __set_errno (0);
+ /* Even though we have not found anything, the result is zero. */
+ retval = 0;
+ }
+
+ out_close:
+ if (sock != -1)
+ close_not_cancel_no_status (sock);
+ out:
+ if (__nscd_drop_map_ref (mapped, &gc_cycle) != 0)
+ {
+ /* When we come here this means there has been a GC cycle while we
+ were looking for the data. This means the data might have been
+ inconsistent. Retry if possible. */
+ if ((gc_cycle & 1) != 0 || ++nretries == 5 || retval == -1)
+ {
+ /* nscd is just running gc now. Disable using the mapping. */
+ if (atomic_decrement_val (&mapped->counter) == 0)
+ __nscd_unmap (mapped);
+ mapped = NO_MAPPING;
+ }
+
+ if (retval != -1)
+ goto retry;
+ }
+
+ return retval;
+}
diff --git a/REORG.TODO/nscd/nscd_getserv_r.c b/REORG.TODO/nscd/nscd_getserv_r.c
new file mode 100644
index 0000000000..7dfb1c828d
--- /dev/null
+++ b/REORG.TODO/nscd/nscd_getserv_r.c
@@ -0,0 +1,388 @@
+/* Copyright (C) 2007-2017 Free Software Foundation, Inc.
+ This file is part of the GNU C Library.
+ Contributed by Ulrich Drepper <drepper@redhat.com>, 2007.
+
+ The GNU C Library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ The GNU C Library 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
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with the GNU C Library; if not, see
+ <http://www.gnu.org/licenses/>. */
+
+#include <assert.h>
+#include <errno.h>
+#include <string.h>
+#include <not-cancel.h>
+#include <_itoa.h>
+#include <stdint.h>
+
+#include "nscd-client.h"
+#include "nscd_proto.h"
+
+
+int __nss_not_use_nscd_services;
+
+
+static int nscd_getserv_r (const char *crit, size_t critlen, const char *proto,
+ request_type type, struct servent *resultbuf,
+ char *buf, size_t buflen, struct servent **result);
+
+
+int
+__nscd_getservbyname_r (const char *name, const char *proto,
+ struct servent *result_buf, char *buf, size_t buflen,
+ struct servent **result)
+{
+ return nscd_getserv_r (name, strlen (name), proto, GETSERVBYNAME, result_buf,
+ buf, buflen, result);
+}
+
+
+int
+__nscd_getservbyport_r (int port, const char *proto,
+ struct servent *result_buf, char *buf, size_t buflen,
+ struct servent **result)
+{
+ char portstr[3 * sizeof (int) + 2];
+ portstr[sizeof (portstr) - 1] = '\0';
+ char *cp = _itoa_word (port, portstr + sizeof (portstr) - 1, 10, 0);
+
+ return nscd_getserv_r (cp, portstr + sizeof (portstr) - 1 - cp, proto,
+ GETSERVBYPORT, result_buf, buf, buflen, result);
+}
+
+
+libc_locked_map_ptr (, __serv_map_handle) attribute_hidden;
+/* Note that we only free the structure if necessary. The memory
+ mapping is not removed since it is not visible to the malloc
+ handling. */
+libc_freeres_fn (serv_map_free)
+{
+ if (__serv_map_handle.mapped != NO_MAPPING)
+ {
+ void *p = __serv_map_handle.mapped;
+ __serv_map_handle.mapped = NO_MAPPING;
+ free (p);
+ }
+}
+
+
+static int
+nscd_getserv_r (const char *crit, size_t critlen, const char *proto,
+ request_type type, struct servent *resultbuf,
+ char *buf, size_t buflen, struct servent **result)
+{
+ int gc_cycle;
+ int nretries = 0;
+ size_t alloca_used = 0;
+
+ /* If the mapping is available, try to search there instead of
+ communicating with the nscd. */
+ struct mapped_database *mapped;
+ mapped = __nscd_get_map_ref (GETFDSERV, "services", &__serv_map_handle,
+ &gc_cycle);
+ size_t protolen = proto == NULL ? 0 : strlen (proto);
+ size_t keylen = critlen + 1 + protolen + 1;
+ int alloca_key = __libc_use_alloca (keylen);
+ char *key;
+ if (alloca_key)
+ key = alloca_account (keylen, alloca_used);
+ else
+ {
+ key = malloc (keylen);
+ if (key == NULL)
+ return -1;
+ }
+ memcpy (__mempcpy (__mempcpy (key, crit, critlen),
+ "/", 1), proto ?: "", protolen + 1);
+
+ retry:;
+ const char *s_name = NULL;
+ const char *s_proto = NULL;
+ int alloca_aliases_len = 0;
+ const uint32_t *aliases_len = NULL;
+ const char *aliases_list = NULL;
+ int retval = -1;
+ const char *recend = (const char *) ~UINTMAX_C (0);
+ int sock = -1;
+ serv_response_header serv_resp;
+
+ if (mapped != NO_MAPPING)
+ {
+ struct datahead *found = __nscd_cache_search (type, key, keylen, mapped,
+ sizeof serv_resp);
+
+ if (found != NULL)
+ {
+ s_name = (char *) (&found->data[0].servdata + 1);
+ serv_resp = found->data[0].servdata;
+ s_proto = s_name + serv_resp.s_name_len;
+ alloca_aliases_len = 1;
+ aliases_len = (uint32_t *) (s_proto + serv_resp.s_proto_len);
+ aliases_list = ((char *) aliases_len
+ + serv_resp.s_aliases_cnt * sizeof (uint32_t));
+ recend = (const char *) found->data + found->recsize;
+ /* Now check if we can trust serv_resp fields. If GC is
+ in progress, it can contain anything. */
+ if (mapped->head->gc_cycle != gc_cycle)
+ {
+ retval = -2;
+ goto out;
+ }
+ if (__builtin_expect ((const char *) aliases_len
+ + serv_resp.s_aliases_cnt * sizeof (uint32_t)
+ > recend, 0))
+ goto out;
+
+#if !_STRING_ARCH_unaligned
+ /* The aliases_len array in the mapped database might very
+ well be unaligned. We will access it word-wise so on
+ platforms which do not tolerate unaligned accesses we
+ need to make an aligned copy. */
+ if (((uintptr_t) aliases_len & (__alignof__ (*aliases_len) - 1))
+ != 0)
+ {
+ uint32_t *tmp;
+ alloca_aliases_len
+ = __libc_use_alloca (alloca_used
+ + (serv_resp.s_aliases_cnt
+ * sizeof (uint32_t)));
+ if (alloca_aliases_len)
+ tmp = alloca_account (serv_resp.s_aliases_cnt
+ * sizeof (uint32_t),
+ alloca_used);
+ else
+ {
+ tmp = malloc (serv_resp.s_aliases_cnt * sizeof (uint32_t));
+ if (tmp == NULL)
+ {
+ retval = ENOMEM;
+ goto out;
+ }
+ }
+ aliases_len = memcpy (tmp, aliases_len,
+ serv_resp.s_aliases_cnt
+ * sizeof (uint32_t));
+ }
+#endif
+ }
+ }
+
+ if (s_name == NULL)
+ {
+ sock = __nscd_open_socket (key, keylen, type, &serv_resp,
+ sizeof (serv_resp));
+ if (sock == -1)
+ {
+ __nss_not_use_nscd_services = 1;
+ goto out;
+ }
+ }
+
+ /* No value found so far. */
+ *result = NULL;
+
+ if (__glibc_unlikely (serv_resp.found == -1))
+ {
+ /* The daemon does not cache this database. */
+ __nss_not_use_nscd_services = 1;
+ goto out_close;
+ }
+
+ if (serv_resp.found == 1)
+ {
+ char *cp = buf;
+ uintptr_t align1;
+ uintptr_t align2;
+ size_t total_len;
+ ssize_t cnt;
+ int n;
+
+ /* A first check whether the buffer is sufficiently large is possible. */
+ /* Now allocate the buffer the array for the group members. We must
+ align the pointer and the base of the h_addr_list pointers. */
+ align1 = ((__alignof__ (char *) - (cp - ((char *) 0)))
+ & (__alignof__ (char *) - 1));
+ align2 = ((__alignof__ (char *) - ((cp + align1 + serv_resp.s_name_len
+ + serv_resp.s_proto_len)
+ - ((char *) 0)))
+ & (__alignof__ (char *) - 1));
+ if (buflen < (align1 + serv_resp.s_name_len + serv_resp.s_proto_len
+ + align2
+ + (serv_resp.s_aliases_cnt + 1) * sizeof (char *)))
+ {
+ no_room:
+ __set_errno (ERANGE);
+ retval = ERANGE;
+ goto out_close;
+ }
+ cp += align1;
+
+ /* Prepare the result as far as we can. */
+ resultbuf->s_aliases = (char **) cp;
+ cp += (serv_resp.s_aliases_cnt + 1) * sizeof (char *);
+
+ resultbuf->s_name = cp;
+ cp += serv_resp.s_name_len;
+ resultbuf->s_proto = cp;
+ cp += serv_resp.s_proto_len + align2;
+ resultbuf->s_port = serv_resp.s_port;
+
+ if (s_name == NULL)
+ {
+ struct iovec vec[2];
+
+ vec[0].iov_base = resultbuf->s_name;
+ vec[0].iov_len = serv_resp.s_name_len + serv_resp.s_proto_len;
+ total_len = vec[0].iov_len;
+ n = 1;
+
+ if (serv_resp.s_aliases_cnt > 0)
+ {
+ assert (alloca_aliases_len == 0);
+ alloca_aliases_len
+ = __libc_use_alloca (alloca_used
+ + (serv_resp.s_aliases_cnt
+ * sizeof (uint32_t)));
+ if (alloca_aliases_len)
+ aliases_len = alloca_account (serv_resp.s_aliases_cnt
+ * sizeof (uint32_t),
+ alloca_used);
+ else
+ {
+ aliases_len = malloc (serv_resp.s_aliases_cnt
+ * sizeof (uint32_t));
+ if (aliases_len == NULL)
+ {
+ retval = ENOMEM;
+ goto out_close;
+ }
+ }
+ vec[n].iov_base = (void *) aliases_len;
+ vec[n].iov_len = serv_resp.s_aliases_cnt * sizeof (uint32_t);
+
+ total_len += serv_resp.s_aliases_cnt * sizeof (uint32_t);
+ ++n;
+ }
+
+ if ((size_t) __readvall (sock, vec, n) != total_len)
+ goto out_close;
+ }
+ else
+ memcpy (resultbuf->s_name, s_name,
+ serv_resp.s_name_len + serv_resp.s_proto_len);
+
+ /* Now we also can read the aliases. */
+ total_len = 0;
+ for (cnt = 0; cnt < serv_resp.s_aliases_cnt; ++cnt)
+ {
+ resultbuf->s_aliases[cnt] = cp;
+ cp += aliases_len[cnt];
+ total_len += aliases_len[cnt];
+ }
+ resultbuf->s_aliases[cnt] = NULL;
+
+ if (__builtin_expect ((const char *) aliases_list + total_len > recend,
+ 0))
+ {
+ /* aliases_len array might contain garbage during nscd GC cycle,
+ retry rather than fail in that case. */
+ if (aliases_list != NULL && mapped->head->gc_cycle != gc_cycle)
+ retval = -2;
+ goto out_close;
+ }
+
+ /* See whether this would exceed the buffer capacity. */
+ if (__glibc_unlikely (cp > buf + buflen))
+ {
+ /* aliases_len array might contain garbage during nscd GC cycle,
+ retry rather than fail in that case. */
+ if (aliases_list != NULL && mapped->head->gc_cycle != gc_cycle)
+ {
+ retval = -2;
+ goto out_close;
+ }
+ goto no_room;
+ }
+
+ /* And finally read the aliases. */
+ if (aliases_list == NULL)
+ {
+ if (total_len == 0
+ || ((size_t) __readall (sock, resultbuf->s_aliases[0], total_len)
+ == total_len))
+ {
+ retval = 0;
+ *result = resultbuf;
+ }
+ }
+ else
+ {
+ memcpy (resultbuf->s_aliases[0], aliases_list, total_len);
+
+ /* Try to detect corrupt databases. */
+ if (resultbuf->s_name[serv_resp.s_name_len - 1] != '\0'
+ || resultbuf->s_proto[serv_resp.s_proto_len - 1] != '\0'
+ || ({for (cnt = 0; cnt < serv_resp.s_aliases_cnt; ++cnt)
+ if (resultbuf->s_aliases[cnt][aliases_len[cnt] - 1]
+ != '\0')
+ break;
+ cnt < serv_resp.s_aliases_cnt; }))
+ {
+ /* We cannot use the database. */
+ if (mapped->head->gc_cycle != gc_cycle)
+ retval = -2;
+ goto out_close;
+ }
+
+ retval = 0;
+ *result = resultbuf;
+ }
+ }
+ else
+ {
+ /* Set errno to 0 to indicate no error, just no found record. */
+ __set_errno (0);
+ /* Even though we have not found anything, the result is zero. */
+ retval = 0;
+ }
+
+ out_close:
+ if (sock != -1)
+ close_not_cancel_no_status (sock);
+ out:
+ if (__nscd_drop_map_ref (mapped, &gc_cycle) != 0)
+ {
+ /* When we come here this means there has been a GC cycle while we
+ were looking for the data. This means the data might have been
+ inconsistent. Retry if possible. */
+ if ((gc_cycle & 1) != 0 || ++nretries == 5 || retval == -1)
+ {
+ /* nscd is just running gc now. Disable using the mapping. */
+ if (atomic_decrement_val (&mapped->counter) == 0)
+ __nscd_unmap (mapped);
+ mapped = NO_MAPPING;
+ }
+
+ if (retval != -1)
+ {
+ if (!alloca_aliases_len)
+ free ((void *) aliases_len);
+ goto retry;
+ }
+ }
+
+ if (!alloca_aliases_len)
+ free ((void *) aliases_len);
+ if (!alloca_key)
+ free (key);
+
+ return retval;
+}
diff --git a/REORG.TODO/nscd/nscd_helper.c b/REORG.TODO/nscd/nscd_helper.c
new file mode 100644
index 0000000000..22905d0b83
--- /dev/null
+++ b/REORG.TODO/nscd/nscd_helper.c
@@ -0,0 +1,564 @@
+/* Copyright (C) 1998-2017 Free Software Foundation, Inc.
+ This file is part of the GNU C Library.
+ Contributed by Ulrich Drepper <drepper@cygnus.com>, 1998.
+
+ The GNU C Library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ The GNU C Library 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
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with the GNU C Library; if not, see
+ <http://www.gnu.org/licenses/>. */
+
+#include <assert.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+#include <stdint.h>
+#include <sys/mman.h>
+#include <sys/poll.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <sys/uio.h>
+#include <sys/un.h>
+#include <not-cancel.h>
+#include <nis/rpcsvc/nis.h>
+#include <kernel-features.h>
+
+#include "nscd-client.h"
+
+
+/* Extra time we wait if the socket is still receiving data. This
+ value is in milliseconds. Note that the other side is nscd on the
+ local machine and it is already transmitting data. So the wait
+ time need not be long. */
+#define EXTRA_RECEIVE_TIME 200
+
+
+static int
+wait_on_socket (int sock, long int usectmo)
+{
+ struct pollfd fds[1];
+ fds[0].fd = sock;
+ fds[0].events = POLLIN | POLLERR | POLLHUP;
+ int n = __poll (fds, 1, usectmo);
+ if (n == -1 && __builtin_expect (errno == EINTR, 0))
+ {
+ /* Handle the case where the poll() call is interrupted by a
+ signal. We cannot just use TEMP_FAILURE_RETRY since it might
+ lead to infinite loops. */
+ struct timeval now;
+ (void) __gettimeofday (&now, NULL);
+ long int end = now.tv_sec * 1000 + usectmo + (now.tv_usec + 500) / 1000;
+ long int timeout = usectmo;
+ while (1)
+ {
+ n = __poll (fds, 1, timeout);
+ if (n != -1 || errno != EINTR)
+ break;
+
+ /* Recompute the timeout time. */
+ (void) __gettimeofday (&now, NULL);
+ timeout = end - (now.tv_sec * 1000 + (now.tv_usec + 500) / 1000);
+ }
+ }
+
+ return n;
+}
+
+
+ssize_t
+__readall (int fd, void *buf, size_t len)
+{
+ size_t n = len;
+ ssize_t ret;
+ do
+ {
+ again:
+ ret = TEMP_FAILURE_RETRY (__read (fd, buf, n));
+ if (ret <= 0)
+ {
+ if (__builtin_expect (ret < 0 && errno == EAGAIN, 0)
+ /* The socket is still receiving data. Wait a bit more. */
+ && wait_on_socket (fd, EXTRA_RECEIVE_TIME) > 0)
+ goto again;
+
+ break;
+ }
+ buf = (char *) buf + ret;
+ n -= ret;
+ }
+ while (n > 0);
+ return ret < 0 ? ret : len - n;
+}
+
+
+ssize_t
+__readvall (int fd, const struct iovec *iov, int iovcnt)
+{
+ ssize_t ret = TEMP_FAILURE_RETRY (__readv (fd, iov, iovcnt));
+ if (ret <= 0)
+ {
+ if (__glibc_likely (ret == 0 || errno != EAGAIN))
+ /* A genuine error or no data to read. */
+ return ret;
+
+ /* The data has not all yet been received. Do as if we have not
+ read anything yet. */
+ ret = 0;
+ }
+
+ size_t total = 0;
+ for (int i = 0; i < iovcnt; ++i)
+ total += iov[i].iov_len;
+
+ if (ret < total)
+ {
+ struct iovec iov_buf[iovcnt];
+ ssize_t r = ret;
+
+ struct iovec *iovp = memcpy (iov_buf, iov, iovcnt * sizeof (*iov));
+ do
+ {
+ while (iovp->iov_len <= r)
+ {
+ r -= iovp->iov_len;
+ --iovcnt;
+ ++iovp;
+ }
+ iovp->iov_base = (char *) iovp->iov_base + r;
+ iovp->iov_len -= r;
+ again:
+ r = TEMP_FAILURE_RETRY (__readv (fd, iovp, iovcnt));
+ if (r <= 0)
+ {
+ if (__builtin_expect (r < 0 && errno == EAGAIN, 0)
+ /* The socket is still receiving data. Wait a bit more. */
+ && wait_on_socket (fd, EXTRA_RECEIVE_TIME) > 0)
+ goto again;
+
+ break;
+ }
+ ret += r;
+ }
+ while (ret < total);
+ if (r < 0)
+ ret = r;
+ }
+ return ret;
+}
+
+
+static int
+open_socket (request_type type, const char *key, size_t keylen)
+{
+ int sock;
+
+ sock = __socket (PF_UNIX, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0);
+ if (sock < 0)
+ return -1;
+
+ size_t real_sizeof_reqdata = sizeof (request_header) + keylen;
+ struct
+ {
+ request_header req;
+ char key[];
+ } *reqdata = alloca (real_sizeof_reqdata);
+
+ struct sockaddr_un sun;
+ sun.sun_family = AF_UNIX;
+ strcpy (sun.sun_path, _PATH_NSCDSOCKET);
+ if (__connect (sock, (struct sockaddr *) &sun, sizeof (sun)) < 0
+ && errno != EINPROGRESS)
+ goto out;
+
+ reqdata->req.version = NSCD_VERSION;
+ reqdata->req.type = type;
+ reqdata->req.key_len = keylen;
+
+ memcpy (reqdata->key, key, keylen);
+
+ bool first_try = true;
+ struct timeval tvend;
+ /* Fake initializing tvend. */
+ asm ("" : "=m" (tvend));
+ while (1)
+ {
+#ifndef MSG_NOSIGNAL
+# define MSG_NOSIGNAL 0
+#endif
+ ssize_t wres = TEMP_FAILURE_RETRY (__send (sock, reqdata,
+ real_sizeof_reqdata,
+ MSG_NOSIGNAL));
+ if (__glibc_likely (wres == (ssize_t) real_sizeof_reqdata))
+ /* We managed to send the request. */
+ return sock;
+
+ if (wres != -1 || errno != EAGAIN)
+ /* Something is really wrong, no chance to continue. */
+ break;
+
+ /* The daemon is busy wait for it. */
+ int to;
+ struct timeval now;
+ (void) __gettimeofday (&now, NULL);
+ if (first_try)
+ {
+ tvend.tv_usec = now.tv_usec;
+ tvend.tv_sec = now.tv_sec + 5;
+ to = 5 * 1000;
+ first_try = false;
+ }
+ else
+ to = ((tvend.tv_sec - now.tv_sec) * 1000
+ + (tvend.tv_usec - now.tv_usec) / 1000);
+
+ struct pollfd fds[1];
+ fds[0].fd = sock;
+ fds[0].events = POLLOUT | POLLERR | POLLHUP;
+ if (__poll (fds, 1, to) <= 0)
+ /* The connection timed out or broke down. */
+ break;
+
+ /* We try to write again. */
+ }
+
+ out:
+ close_not_cancel_no_status (sock);
+
+ return -1;
+}
+
+
+void
+__nscd_unmap (struct mapped_database *mapped)
+{
+ assert (mapped->counter == 0);
+ __munmap ((void *) mapped->head, mapped->mapsize);
+ free (mapped);
+}
+
+
+/* Try to get a file descriptor for the shared meory segment
+ containing the database. */
+struct mapped_database *
+__nscd_get_mapping (request_type type, const char *key,
+ struct mapped_database **mappedp)
+{
+ struct mapped_database *result = NO_MAPPING;
+#ifdef SCM_RIGHTS
+ const size_t keylen = strlen (key) + 1;
+ int saved_errno = errno;
+
+ int mapfd = -1;
+ char resdata[keylen];
+
+ /* Open a socket and send the request. */
+ int sock = open_socket (type, key, keylen);
+ if (sock < 0)
+ goto out;
+
+ /* Room for the data sent along with the file descriptor. We expect
+ the key name back. */
+ uint64_t mapsize;
+ struct iovec iov[2];
+ iov[0].iov_base = resdata;
+ iov[0].iov_len = keylen;
+ iov[1].iov_base = &mapsize;
+ iov[1].iov_len = sizeof (mapsize);
+
+ union
+ {
+ struct cmsghdr hdr;
+ char bytes[CMSG_SPACE (sizeof (int))];
+ } buf;
+ struct msghdr msg = { .msg_iov = iov, .msg_iovlen = 2,
+ .msg_control = buf.bytes,
+ .msg_controllen = sizeof (buf) };
+ struct cmsghdr *cmsg = CMSG_FIRSTHDR (&msg);
+
+ cmsg->cmsg_level = SOL_SOCKET;
+ cmsg->cmsg_type = SCM_RIGHTS;
+ cmsg->cmsg_len = CMSG_LEN (sizeof (int));
+
+ /* This access is well-aligned since BUF is correctly aligned for an
+ int and CMSG_DATA preserves this alignment. */
+ memset (CMSG_DATA (cmsg), '\xff', sizeof (int));
+
+ msg.msg_controllen = cmsg->cmsg_len;
+
+ if (wait_on_socket (sock, 5 * 1000) <= 0)
+ goto out_close2;
+
+# ifndef MSG_CMSG_CLOEXEC
+# define MSG_CMSG_CLOEXEC 0
+# endif
+ ssize_t n = TEMP_FAILURE_RETRY (__recvmsg (sock, &msg, MSG_CMSG_CLOEXEC));
+
+ if (__builtin_expect (CMSG_FIRSTHDR (&msg) == NULL
+ || (CMSG_FIRSTHDR (&msg)->cmsg_len
+ != CMSG_LEN (sizeof (int))), 0))
+ goto out_close2;
+
+ int *ip = (void *) CMSG_DATA (cmsg);
+ mapfd = *ip;
+
+ if (__glibc_unlikely (n != keylen && n != keylen + sizeof (mapsize)))
+ goto out_close;
+
+ if (__glibc_unlikely (strcmp (resdata, key) != 0))
+ goto out_close;
+
+ if (__glibc_unlikely (n == keylen))
+ {
+ struct stat64 st;
+ if (__builtin_expect (fstat64 (mapfd, &st) != 0, 0)
+ || __builtin_expect (st.st_size < sizeof (struct database_pers_head),
+ 0))
+ goto out_close;
+
+ mapsize = st.st_size;
+ }
+
+ /* The file is large enough, map it now. */
+ void *mapping = __mmap (NULL, mapsize, PROT_READ, MAP_SHARED, mapfd, 0);
+ if (__glibc_likely (mapping != MAP_FAILED))
+ {
+ /* Check whether the database is correct and up-to-date. */
+ struct database_pers_head *head = mapping;
+
+ if (__builtin_expect (head->version != DB_VERSION, 0)
+ || __builtin_expect (head->header_size != sizeof (*head), 0)
+ /* Catch some misconfiguration. The server should catch
+ them now but some older versions did not. */
+ || __builtin_expect (head->module == 0, 0)
+ /* This really should not happen but who knows, maybe the update
+ thread got stuck. */
+ || __builtin_expect (! head->nscd_certainly_running
+ && (head->timestamp + MAPPING_TIMEOUT
+ < time (NULL)), 0))
+ {
+ out_unmap:
+ __munmap (mapping, mapsize);
+ goto out_close;
+ }
+
+ size_t size = (sizeof (*head) + roundup (head->module * sizeof (ref_t),
+ ALIGN)
+ + head->data_size);
+
+ if (__glibc_unlikely (mapsize < size))
+ goto out_unmap;
+
+ /* Allocate a record for the mapping. */
+ struct mapped_database *newp = malloc (sizeof (*newp));
+ if (newp == NULL)
+ /* Ugh, after all we went through the memory allocation failed. */
+ goto out_unmap;
+
+ newp->head = mapping;
+ newp->data = ((char *) mapping + head->header_size
+ + roundup (head->module * sizeof (ref_t), ALIGN));
+ newp->mapsize = size;
+ newp->datasize = head->data_size;
+ /* Set counter to 1 to show it is usable. */
+ newp->counter = 1;
+
+ result = newp;
+ }
+
+ out_close:
+ __close (mapfd);
+ out_close2:
+ __close (sock);
+ out:
+ __set_errno (saved_errno);
+#endif /* SCM_RIGHTS */
+
+ struct mapped_database *oldval = *mappedp;
+ *mappedp = result;
+
+ if (oldval != NULL && atomic_decrement_val (&oldval->counter) == 0)
+ __nscd_unmap (oldval);
+
+ return result;
+}
+
+struct mapped_database *
+__nscd_get_map_ref (request_type type, const char *name,
+ volatile struct locked_map_ptr *mapptr, int *gc_cyclep)
+{
+ struct mapped_database *cur = mapptr->mapped;
+ if (cur == NO_MAPPING)
+ return cur;
+
+ if (!__nscd_acquire_maplock (mapptr))
+ return NO_MAPPING;
+
+ cur = mapptr->mapped;
+
+ if (__glibc_likely (cur != NO_MAPPING))
+ {
+ /* If not mapped or timestamp not updated, request new map. */
+ if (cur == NULL
+ || (cur->head->nscd_certainly_running == 0
+ && cur->head->timestamp + MAPPING_TIMEOUT < time (NULL))
+ || cur->head->data_size > cur->datasize)
+ cur = __nscd_get_mapping (type, name,
+ (struct mapped_database **) &mapptr->mapped);
+
+ if (__glibc_likely (cur != NO_MAPPING))
+ {
+ if (__builtin_expect (((*gc_cyclep = cur->head->gc_cycle) & 1) != 0,
+ 0))
+ cur = NO_MAPPING;
+ else
+ atomic_increment (&cur->counter);
+ }
+ }
+
+ mapptr->lock = 0;
+
+ return cur;
+}
+
+
+/* Using sizeof (hashentry) is not always correct to determine the size of
+ the data structure as found in the nscd cache. The program could be
+ a 64-bit process and nscd could be a 32-bit process. In this case
+ sizeof (hashentry) would overestimate the size. The following is
+ the minimum size of such an entry, good enough for our tests here. */
+#define MINIMUM_HASHENTRY_SIZE \
+ (offsetof (struct hashentry, dellist) + sizeof (int32_t))
+
+
+/* Don't return const struct datahead *, as eventhough the record
+ is normally constant, it can change arbitrarily during nscd
+ garbage collection. */
+struct datahead *
+__nscd_cache_search (request_type type, const char *key, size_t keylen,
+ const struct mapped_database *mapped, size_t datalen)
+{
+ unsigned long int hash = __nis_hash (key, keylen) % mapped->head->module;
+ size_t datasize = mapped->datasize;
+
+ ref_t trail = mapped->head->array[hash];
+ trail = atomic_forced_read (trail);
+ ref_t work = trail;
+ size_t loop_cnt = datasize / (MINIMUM_HASHENTRY_SIZE
+ + offsetof (struct datahead, data) / 2);
+ int tick = 0;
+
+ while (work != ENDREF && work + MINIMUM_HASHENTRY_SIZE <= datasize)
+ {
+ struct hashentry *here = (struct hashentry *) (mapped->data + work);
+ ref_t here_key, here_packet;
+
+#if !_STRING_ARCH_unaligned
+ /* Although during garbage collection when moving struct hashentry
+ records around we first copy from old to new location and then
+ adjust pointer from previous hashentry to it, there is no barrier
+ between those memory writes. It is very unlikely to hit it,
+ so check alignment only if a misaligned load can crash the
+ application. */
+ if ((uintptr_t) here & (__alignof__ (*here) - 1))
+ return NULL;
+#endif
+
+ if (type == here->type
+ && keylen == here->len
+ && (here_key = atomic_forced_read (here->key)) + keylen <= datasize
+ && memcmp (key, mapped->data + here_key, keylen) == 0
+ && ((here_packet = atomic_forced_read (here->packet))
+ + sizeof (struct datahead) <= datasize))
+ {
+ /* We found the entry. Increment the appropriate counter. */
+ struct datahead *dh
+ = (struct datahead *) (mapped->data + here_packet);
+
+#if !_STRING_ARCH_unaligned
+ if ((uintptr_t) dh & (__alignof__ (*dh) - 1))
+ return NULL;
+#endif
+
+ /* See whether we must ignore the entry or whether something
+ is wrong because garbage collection is in progress. */
+ if (dh->usable
+ && here_packet + dh->allocsize <= datasize
+ && (here_packet + offsetof (struct datahead, data) + datalen
+ <= datasize))
+ return dh;
+ }
+
+ work = atomic_forced_read (here->next);
+ /* Prevent endless loops. This should never happen but perhaps
+ the database got corrupted, accidentally or deliberately. */
+ if (work == trail || loop_cnt-- == 0)
+ break;
+ if (tick)
+ {
+ struct hashentry *trailelem;
+ trailelem = (struct hashentry *) (mapped->data + trail);
+
+#if !_STRING_ARCH_unaligned
+ /* We have to redo the checks. Maybe the data changed. */
+ if ((uintptr_t) trailelem & (__alignof__ (*trailelem) - 1))
+ return NULL;
+#endif
+
+ if (trail + MINIMUM_HASHENTRY_SIZE > datasize)
+ return NULL;
+
+ trail = atomic_forced_read (trailelem->next);
+ }
+ tick = 1 - tick;
+ }
+
+ return NULL;
+}
+
+
+/* Create a socket connected to a name. */
+int
+__nscd_open_socket (const char *key, size_t keylen, request_type type,
+ void *response, size_t responselen)
+{
+ /* This should never happen and it is something the nscd daemon
+ enforces, too. He it helps to limit the amount of stack
+ used. */
+ if (keylen > MAXKEYLEN)
+ return -1;
+
+ int saved_errno = errno;
+
+ int sock = open_socket (type, key, keylen);
+ if (sock >= 0)
+ {
+ /* Wait for data. */
+ if (wait_on_socket (sock, 5 * 1000) > 0)
+ {
+ ssize_t nbytes = TEMP_FAILURE_RETRY (__read (sock, response,
+ responselen));
+ if (nbytes == (ssize_t) responselen)
+ return sock;
+ }
+
+ close_not_cancel_no_status (sock);
+ }
+
+ __set_errno (saved_errno);
+
+ return -1;
+}
diff --git a/REORG.TODO/nscd/nscd_initgroups.c b/REORG.TODO/nscd/nscd_initgroups.c
new file mode 100644
index 0000000000..00c650896a
--- /dev/null
+++ b/REORG.TODO/nscd/nscd_initgroups.c
@@ -0,0 +1,180 @@
+/* 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.
+
+ The GNU C Library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ The GNU C Library 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
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with the GNU C Library; if not, see
+ <http://www.gnu.org/licenses/>. */
+
+#include <assert.h>
+#include <errno.h>
+#include <grp.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <not-cancel.h>
+
+#include "nscd-client.h"
+#include "nscd_proto.h"
+
+
+/* We use the same mapping as in nscd_getgr. */
+libc_locked_map_ptr (extern, __gr_map_handle) attribute_hidden;
+
+
+int
+__nscd_getgrouplist (const char *user, gid_t group, long int *size,
+ gid_t **groupsp, long int limit)
+{
+ size_t userlen = strlen (user) + 1;
+ int gc_cycle;
+ int nretries = 0;
+
+ /* If the mapping is available, try to search there instead of
+ communicating with the nscd. */
+ struct mapped_database *mapped;
+ mapped = __nscd_get_map_ref (GETFDGR, "group", &__gr_map_handle, &gc_cycle);
+
+ retry:;
+ char *respdata = NULL;
+ int retval = -1;
+ int sock = -1;
+ initgr_response_header initgr_resp;
+
+ if (mapped != NO_MAPPING)
+ {
+ struct datahead *found = __nscd_cache_search (INITGROUPS, user,
+ userlen, mapped,
+ sizeof initgr_resp);
+ if (found != NULL)
+ {
+ respdata = (char *) (&found->data[0].initgrdata + 1);
+ initgr_resp = found->data[0].initgrdata;
+ char *recend = (char *) found->data + found->recsize;
+
+ /* Now check if we can trust initgr_resp fields. If GC is
+ in progress, it can contain anything. */
+ if (mapped->head->gc_cycle != gc_cycle)
+ {
+ retval = -2;
+ goto out;
+ }
+
+ if (respdata + initgr_resp.ngrps * sizeof (int32_t) > recend)
+ goto out;
+ }
+ }
+
+ /* If we do not have the cache mapped, try to get the data over the
+ socket. */
+ if (respdata == NULL)
+ {
+ sock = __nscd_open_socket (user, userlen, INITGROUPS, &initgr_resp,
+ sizeof (initgr_resp));
+ if (sock == -1)
+ {
+ /* nscd not running or wrong version. */
+ __nss_not_use_nscd_group = 1;
+ goto out;
+ }
+ }
+
+ if (initgr_resp.found == 1)
+ {
+ /* The following code assumes that gid_t and int32_t are the
+ same size. This is the case for al existing implementation.
+ If this should change some code needs to be added which
+ doesn't use memcpy but instead copies each array element one
+ by one. */
+ assert (sizeof (int32_t) == sizeof (gid_t));
+ assert (initgr_resp.ngrps >= 0);
+
+ /* Make sure we have enough room. We always count GROUP in even
+ though we might not end up adding it. */
+ if (*size < initgr_resp.ngrps + 1)
+ {
+ gid_t *newp = realloc (*groupsp,
+ (initgr_resp.ngrps + 1) * sizeof (gid_t));
+ if (newp == NULL)
+ /* We cannot increase the buffer size. */
+ goto out_close;
+
+ *groupsp = newp;
+ *size = initgr_resp.ngrps + 1;
+ }
+
+ if (respdata == NULL)
+ {
+ /* Read the data from the socket. */
+ if ((size_t) __readall (sock, *groupsp, initgr_resp.ngrps
+ * sizeof (gid_t))
+ == initgr_resp.ngrps * sizeof (gid_t))
+ retval = initgr_resp.ngrps;
+ }
+ else
+ {
+ /* Just copy the data. */
+ retval = initgr_resp.ngrps;
+ memcpy (*groupsp, respdata, retval * sizeof (gid_t));
+ }
+ }
+ else
+ {
+ if (__glibc_unlikely (initgr_resp.found == -1))
+ {
+ /* The daemon does not cache this database. */
+ __nss_not_use_nscd_group = 1;
+ goto out_close;
+ }
+
+ /* No group found yet. */
+ retval = 0;
+
+ assert (*size >= 1);
+ }
+
+ /* Check whether GROUP is part of the mix. If not, add it. */
+ if (retval >= 0)
+ {
+ int cnt;
+ for (cnt = 0; cnt < retval; ++cnt)
+ if ((*groupsp)[cnt] == group)
+ break;
+
+ if (cnt == retval)
+ (*groupsp)[retval++] = group;
+ }
+
+ out_close:
+ if (sock != -1)
+ close_not_cancel_no_status (sock);
+ out:
+ if (__nscd_drop_map_ref (mapped, &gc_cycle) != 0)
+ {
+ /* When we come here this means there has been a GC cycle while we
+ were looking for the data. This means the data might have been
+ inconsistent. Retry if possible. */
+ if ((gc_cycle & 1) != 0 || ++nretries == 5 || retval == -1)
+ {
+ /* nscd is just running gc now. Disable using the mapping. */
+ if (atomic_decrement_val (&mapped->counter) == 0)
+ __nscd_unmap (mapped);
+ mapped = NO_MAPPING;
+ }
+
+ if (retval != -1)
+ goto retry;
+ }
+
+ return retval;
+}
diff --git a/REORG.TODO/nscd/nscd_netgroup.c b/REORG.TODO/nscd/nscd_netgroup.c
new file mode 100644
index 0000000000..44f37ef957
--- /dev/null
+++ b/REORG.TODO/nscd/nscd_netgroup.c
@@ -0,0 +1,289 @@
+/* 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.
+
+ The GNU C Library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ The GNU C Library 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
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with the GNU C Library; if not, see
+ <http://www.gnu.org/licenses/>. */
+
+#include <alloca.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+#include <not-cancel.h>
+
+#include "nscd-client.h"
+#include "nscd_proto.h"
+
+int __nss_not_use_nscd_netgroup;
+
+
+libc_locked_map_ptr (static, map_handle);
+/* Note that we only free the structure if necessary. The memory
+ mapping is not removed since it is not visible to the malloc
+ handling. */
+libc_freeres_fn (pw_map_free)
+{
+ if (map_handle.mapped != NO_MAPPING)
+ {
+ void *p = map_handle.mapped;
+ map_handle.mapped = NO_MAPPING;
+ free (p);
+ }
+}
+
+
+int
+__nscd_setnetgrent (const char *group, struct __netgrent *datap)
+{
+ int gc_cycle;
+ int nretries = 0;
+ size_t group_len = strlen (group) + 1;
+
+ /* If the mapping is available, try to search there instead of
+ communicating with the nscd. */
+ struct mapped_database *mapped;
+ mapped = __nscd_get_map_ref (GETFDNETGR, "netgroup", &map_handle, &gc_cycle);
+
+ retry:;
+ char *respdata = NULL;
+ int retval = -1;
+ netgroup_response_header netgroup_resp;
+
+ if (mapped != NO_MAPPING)
+ {
+ struct datahead *found = __nscd_cache_search (GETNETGRENT, group,
+ group_len, mapped,
+ sizeof netgroup_resp);
+ if (found != NULL)
+ {
+ respdata = (char *) (&found->data[0].netgroupdata + 1);
+ netgroup_resp = found->data[0].netgroupdata;
+ /* Now check if we can trust pw_resp fields. If GC is
+ in progress, it can contain anything. */
+ if (mapped->head->gc_cycle != gc_cycle)
+ {
+ retval = -2;
+ goto out;
+ }
+ }
+ }
+
+ int sock = -1;
+ if (respdata == NULL)
+ {
+ sock = __nscd_open_socket (group, group_len, GETNETGRENT,
+ &netgroup_resp, sizeof (netgroup_resp));
+ if (sock == -1)
+ {
+ /* nscd not running or wrong version. */
+ __nss_not_use_nscd_netgroup = 1;
+ goto out;
+ }
+ }
+
+ if (netgroup_resp.found == 1)
+ {
+ size_t datalen = netgroup_resp.result_len;
+
+ /* If we do not have to read the data here it comes from the
+ mapped data and does not have to be freed. */
+ if (respdata == NULL)
+ {
+ /* The data will come via the socket. */
+ respdata = malloc (datalen);
+ if (respdata == NULL)
+ goto out_close;
+
+ if ((size_t) __readall (sock, respdata, datalen) != datalen)
+ {
+ free (respdata);
+ goto out_close;
+ }
+ }
+
+ datap->data = respdata;
+ datap->data_size = datalen;
+ datap->cursor = respdata;
+ datap->first = 1;
+ datap->nip = (service_user *) -1l;
+ datap->known_groups = NULL;
+ datap->needed_groups = NULL;
+
+ retval = 1;
+ }
+ else
+ {
+ if (__glibc_unlikely (netgroup_resp.found == -1))
+ {
+ /* The daemon does not cache this database. */
+ __nss_not_use_nscd_netgroup = 1;
+ goto out_close;
+ }
+
+ /* Set errno to 0 to indicate no error, just no found record. */
+ __set_errno (0);
+ /* Even though we have not found anything, the result is zero. */
+ retval = 0;
+ }
+
+ out_close:
+ if (sock != -1)
+ close_not_cancel_no_status (sock);
+ out:
+ if (__nscd_drop_map_ref (mapped, &gc_cycle) != 0)
+ {
+ /* When we come here this means there has been a GC cycle while we
+ were looking for the data. This means the data might have been
+ inconsistent. Retry if possible. */
+ if ((gc_cycle & 1) != 0 || ++nretries == 5 || retval == -1)
+ {
+ /* nscd is just running gc now. Disable using the mapping. */
+ if (atomic_decrement_val (&mapped->counter) == 0)
+ __nscd_unmap (mapped);
+ mapped = NO_MAPPING;
+ }
+
+ if (retval != -1)
+ goto retry;
+ }
+
+ return retval;
+}
+
+
+int
+__nscd_innetgr (const char *netgroup, const char *host, const char *user,
+ const char *domain)
+{
+ size_t key_len = (strlen (netgroup) + strlen (host ?: "")
+ + strlen (user ?: "") + strlen (domain ?: "") + 7);
+ char *key;
+ bool use_alloca = __libc_use_alloca (key_len);
+ if (use_alloca)
+ key = alloca (key_len);
+ else
+ {
+ key = malloc (key_len);
+ if (key == NULL)
+ return -1;
+ }
+ char *wp = stpcpy (key, netgroup) + 1;
+ if (host != NULL)
+ {
+ *wp++ = '\1';
+ wp = stpcpy (wp, host) + 1;
+ }
+ else
+ *wp++ = '\0';
+ if (user != NULL)
+ {
+ *wp++ = '\1';
+ wp = stpcpy (wp, user) + 1;
+ }
+ else
+ *wp++ = '\0';
+ if (domain != NULL)
+ {
+ *wp++ = '\1';
+ wp = stpcpy (wp, domain) + 1;
+ }
+ else
+ *wp++ = '\0';
+ key_len = wp - key;
+
+ /* If the mapping is available, try to search there instead of
+ communicating with the nscd. */
+ int gc_cycle;
+ int nretries = 0;
+ struct mapped_database *mapped;
+ mapped = __nscd_get_map_ref (GETFDNETGR, "netgroup", &map_handle, &gc_cycle);
+
+ retry:;
+ int retval = -1;
+ innetgroup_response_header innetgroup_resp;
+ int sock = -1;
+
+ if (mapped != NO_MAPPING)
+ {
+ struct datahead *found = __nscd_cache_search (INNETGR, key,
+ key_len, mapped,
+ sizeof innetgroup_resp);
+ if (found != NULL)
+ {
+ innetgroup_resp = found->data[0].innetgroupdata;
+ /* Now check if we can trust pw_resp fields. If GC is
+ in progress, it can contain anything. */
+ if (mapped->head->gc_cycle != gc_cycle)
+ {
+ retval = -2;
+ goto out;
+ }
+
+ goto found_entry;
+ }
+ }
+
+ sock = __nscd_open_socket (key, key_len, INNETGR,
+ &innetgroup_resp, sizeof (innetgroup_resp));
+ if (sock == -1)
+ {
+ /* nscd not running or wrong version. */
+ __nss_not_use_nscd_netgroup = 1;
+ goto out;
+ }
+
+ found_entry:
+ if (innetgroup_resp.found == 1)
+ retval = innetgroup_resp.result;
+ else
+ {
+ if (__glibc_unlikely (innetgroup_resp.found == -1))
+ {
+ /* The daemon does not cache this database. */
+ __nss_not_use_nscd_netgroup = 1;
+ goto out_close;
+ }
+
+ /* Set errno to 0 to indicate no error, just no found record. */
+ __set_errno (0);
+ /* Even though we have not found anything, the result is zero. */
+ retval = 0;
+ }
+
+ out_close:
+ if (sock != -1)
+ close_not_cancel_no_status (sock);
+ out:
+ if (__nscd_drop_map_ref (mapped, &gc_cycle) != 0)
+ {
+ /* When we come here this means there has been a GC cycle while we
+ were looking for the data. This means the data might have been
+ inconsistent. Retry if possible. */
+ if ((gc_cycle & 1) != 0 || ++nretries == 5 || retval == -1)
+ {
+ /* nscd is just running gc now. Disable using the mapping. */
+ if (atomic_decrement_val (&mapped->counter) == 0)
+ __nscd_unmap (mapped);
+ mapped = NO_MAPPING;
+ }
+
+ if (retval != -1)
+ goto retry;
+ }
+
+ if (! use_alloca)
+ free (key);
+
+ return retval;
+}
diff --git a/REORG.TODO/nscd/nscd_proto.h b/REORG.TODO/nscd/nscd_proto.h
new file mode 100644
index 0000000000..7c61821e74
--- /dev/null
+++ b/REORG.TODO/nscd/nscd_proto.h
@@ -0,0 +1,79 @@
+/* Copyright (C) 1998-2017 Free Software Foundation, Inc.
+ This file is part of the GNU C Library.
+ Contributed by Thorsten Kukuk <kukuk@suse.de>, 1998.
+
+ The GNU C Library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ The GNU C Library 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
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with the GNU C Library; if not, see
+ <http://www.gnu.org/licenses/>. */
+
+#ifndef _NSCD_PROTO_H
+#define _NSCD_PROTO_H 1
+
+#include <grp.h>
+#include <netdb.h>
+#include <pwd.h>
+
+/* Interval in which we transfer retry to contact the NSCD. */
+#define NSS_NSCD_RETRY 100
+
+/* Type needed in the interfaces. */
+struct nscd_ai_result;
+
+
+/* Variables for communication between NSCD handler functions and NSS. */
+extern int __nss_not_use_nscd_passwd attribute_hidden;
+extern int __nss_not_use_nscd_group attribute_hidden;
+extern int __nss_not_use_nscd_hosts attribute_hidden;
+extern int __nss_not_use_nscd_services attribute_hidden;
+extern int __nss_not_use_nscd_netgroup attribute_hidden;
+
+extern int __nscd_getpwnam_r (const char *name, struct passwd *resultbuf,
+ char *buffer, size_t buflen,
+ struct passwd **result);
+extern int __nscd_getpwuid_r (uid_t uid, struct passwd *resultbuf,
+ char *buffer, size_t buflen,
+ struct passwd **result);
+extern int __nscd_getgrnam_r (const char *name, struct group *resultbuf,
+ char *buffer, size_t buflen,
+ struct group **result);
+extern int __nscd_getgrgid_r (gid_t gid, struct group *resultbuf,
+ char *buffer, size_t buflen,
+ struct group **result);
+extern int __nscd_gethostbyname_r (const char *name,
+ struct hostent *resultbuf,
+ char *buffer, size_t buflen,
+ struct hostent **result, int *h_errnop);
+extern int __nscd_gethostbyname2_r (const char *name, int af,
+ struct hostent *resultbuf,
+ char *buffer, size_t buflen,
+ struct hostent **result, int *h_errnop);
+extern int __nscd_gethostbyaddr_r (const void *addr, socklen_t len, int type,
+ struct hostent *resultbuf,
+ char *buffer, size_t buflen,
+ struct hostent **result, int *h_errnop);
+extern int __nscd_getai (const char *key, struct nscd_ai_result **result,
+ int *h_errnop);
+extern int __nscd_getgrouplist (const char *user, gid_t group, long int *size,
+ gid_t **groupsp, long int limit);
+extern int __nscd_getservbyname_r (const char *name, const char *proto,
+ struct servent *result_buf, char *buf,
+ size_t buflen, struct servent **result);
+extern int __nscd_getservbyport_r (int port, const char *proto,
+ struct servent *result_buf, char *buf,
+ size_t buflen, struct servent **result);
+extern int __nscd_innetgr (const char *netgroup, const char *host,
+ const char *user, const char *domain);
+extern int __nscd_setnetgrent (const char *group, struct __netgrent *datap);
+
+
+#endif /* _NSCD_PROTO_H */
diff --git a/REORG.TODO/nscd/nscd_setup_thread.c b/REORG.TODO/nscd/nscd_setup_thread.c
new file mode 100644
index 0000000000..c3670ad943
--- /dev/null
+++ b/REORG.TODO/nscd/nscd_setup_thread.c
@@ -0,0 +1,27 @@
+/* Setup of nscd worker threads. Stub verison.
+ 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 <nscd.h>
+
+
+int
+setup_thread (struct database_dyn *db)
+{
+ /* Nothing. */
+ return 0;
+}
diff --git a/REORG.TODO/nscd/nscd_stat.c b/REORG.TODO/nscd/nscd_stat.c
new file mode 100644
index 0000000000..feb1c98ac3
--- /dev/null
+++ b/REORG.TODO/nscd/nscd_stat.c
@@ -0,0 +1,318 @@
+/* Copyright (c) 1998-2017 Free Software Foundation, Inc.
+ This file is part of the GNU C Library.
+ Contributed by Thorsten Kukuk <kukuk@vt.uni-paderborn.de>, 1998.
+
+ The GNU C Library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ The GNU C Library 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
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with the GNU C Library; if not, see
+ <http://www.gnu.org/licenses/>. */
+
+#include <errno.h>
+#include <error.h>
+#include <inttypes.h>
+#include <langinfo.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/socket.h>
+#include <unistd.h>
+#include <libintl.h>
+
+#include "nscd.h"
+#include "dbg_log.h"
+#include "selinux.h"
+#ifdef HAVE_SELINUX
+# include <selinux/selinux.h>
+# include <selinux/avc.h>
+#endif /* HAVE_SELINUX */
+
+
+/* We use this to make sure the receiver is the same. */
+static const char compilation[21] = __DATE__ " " __TIME__;
+
+/* Statistic data for one database. */
+struct dbstat
+{
+ int enabled;
+ int check_file;
+ int shared;
+ int persistent;
+ size_t module;
+
+ unsigned long int postimeout;
+ unsigned long int negtimeout;
+
+ size_t nentries;
+ size_t maxnentries;
+ size_t maxnsearched;
+ size_t datasize;
+ size_t dataused;
+
+ uintmax_t poshit;
+ uintmax_t neghit;
+ uintmax_t posmiss;
+ uintmax_t negmiss;
+
+ uintmax_t rdlockdelayed;
+ uintmax_t wrlockdelayed;
+
+ uintmax_t addfailed;
+};
+
+/* Record for transmitting statistics. */
+struct statdata
+{
+ char version[sizeof (compilation)];
+ int debug_level;
+ time_t runtime;
+ unsigned long int client_queued;
+ int nthreads;
+ int max_nthreads;
+ int paranoia;
+ time_t restart_interval;
+ unsigned int reload_count;
+ int ndbs;
+ struct dbstat dbs[lastdb];
+#ifdef HAVE_SELINUX
+ struct avc_cache_stats cstats;
+#endif /* HAVE_SELINUX */
+};
+
+
+void
+send_stats (int fd, struct database_dyn dbs[lastdb])
+{
+ struct statdata data;
+ int cnt;
+
+ memset (&data, 0, sizeof (data));
+
+ memcpy (data.version, compilation, sizeof (compilation));
+ data.debug_level = debug_level;
+ data.runtime = time (NULL) - start_time;
+ data.client_queued = client_queued;
+ data.nthreads = nthreads;
+ data.max_nthreads = max_nthreads;
+ data.paranoia = paranoia;
+ data.restart_interval = restart_interval;
+ data.reload_count = reload_count;
+ data.ndbs = lastdb;
+
+ for (cnt = 0; cnt < lastdb; ++cnt)
+ {
+ memset (&data.dbs[cnt], 0, sizeof (data.dbs[cnt]));
+ data.dbs[cnt].enabled = dbs[cnt].enabled;
+ data.dbs[cnt].check_file = dbs[cnt].check_file;
+ data.dbs[cnt].shared = dbs[cnt].shared;
+ data.dbs[cnt].persistent = dbs[cnt].persistent;
+ data.dbs[cnt].postimeout = dbs[cnt].postimeout;
+ data.dbs[cnt].negtimeout = dbs[cnt].negtimeout;
+ if (dbs[cnt].head != NULL)
+ {
+ data.dbs[cnt].module = dbs[cnt].head->module;
+ data.dbs[cnt].poshit = dbs[cnt].head->poshit;
+ data.dbs[cnt].neghit = dbs[cnt].head->neghit;
+ data.dbs[cnt].posmiss = dbs[cnt].head->posmiss;
+ data.dbs[cnt].negmiss = dbs[cnt].head->negmiss;
+ data.dbs[cnt].nentries = dbs[cnt].head->nentries;
+ data.dbs[cnt].maxnentries = dbs[cnt].head->maxnentries;
+ data.dbs[cnt].datasize = dbs[cnt].head->data_size;
+ data.dbs[cnt].dataused = dbs[cnt].head->first_free;
+ data.dbs[cnt].maxnsearched = dbs[cnt].head->maxnsearched;
+ data.dbs[cnt].rdlockdelayed = dbs[cnt].head->rdlockdelayed;
+ data.dbs[cnt].wrlockdelayed = dbs[cnt].head->wrlockdelayed;
+ data.dbs[cnt].addfailed = dbs[cnt].head->addfailed;
+ }
+ }
+
+ if (selinux_enabled)
+ nscd_avc_cache_stats (&data.cstats);
+
+ if (TEMP_FAILURE_RETRY (send (fd, &data, sizeof (data), MSG_NOSIGNAL))
+ != sizeof (data))
+ {
+ char buf[256];
+ dbg_log (_("cannot write statistics: %s"),
+ strerror_r (errno, buf, sizeof (buf)));
+ }
+}
+
+
+int
+receive_print_stats (void)
+{
+ struct statdata data;
+ request_header req;
+ ssize_t nbytes;
+ int fd;
+ int i;
+ uid_t uid = getuid ();
+ const char *yesstr = _("yes");
+ const char *nostr = _("no");
+
+ /* Find out whether there is another user but root allowed to
+ request statistics. */
+ if (uid != 0)
+ {
+ /* User specified? */
+ if(stat_user == NULL || stat_uid != uid)
+ {
+ if (stat_user != NULL)
+ error (EXIT_FAILURE, 0,
+ _("Only root or %s is allowed to use this option!"),
+ stat_user);
+ else
+ error (EXIT_FAILURE, 0,
+ _("Only root is allowed to use this option!"));
+ }
+ }
+
+ /* Open a socket to the running nscd. */
+ fd = nscd_open_socket ();
+ if (fd == -1)
+ error (EXIT_FAILURE, 0, _("nscd not running!\n"));
+
+ /* Send the request. */
+ req.version = NSCD_VERSION;
+ req.type = GETSTAT;
+ req.key_len = 0;
+ nbytes = TEMP_FAILURE_RETRY (send (fd, &req, sizeof (request_header),
+ MSG_NOSIGNAL));
+ if (nbytes != sizeof (request_header))
+ {
+ int err = errno;
+ close (fd);
+ error (EXIT_FAILURE, err, _("write incomplete"));
+ }
+
+ /* Read as much data as we expect. */
+ if (TEMP_FAILURE_RETRY (read (fd, &data, sizeof (data))) != sizeof (data)
+ || (memcmp (data.version, compilation, sizeof (compilation)) != 0
+ /* Yes, this is an assignment! */
+ && (errno = EINVAL)))
+ {
+ /* Not the right version. */
+ int err = errno;
+ close (fd);
+ error (EXIT_FAILURE, err, _("cannot read statistics data"));
+ }
+
+ printf (_("nscd configuration:\n\n%15d server debug level\n"),
+ data.debug_level);
+
+ /* We know that we can simply subtract time_t values. */
+ unsigned long int diff = data.runtime;
+ unsigned int ndays = 0;
+ unsigned int nhours = 0;
+ unsigned int nmins = 0;
+ if (diff > 24 * 60 * 60)
+ {
+ ndays = diff / (24 * 60 * 60);
+ diff %= 24 * 60 * 60;
+ }
+ if (diff > 60 * 60)
+ {
+ nhours = diff / (60 * 60);
+ diff %= 60 * 60;
+ }
+ if (diff > 60)
+ {
+ nmins = diff / 60;
+ diff %= 60;
+ }
+ if (ndays != 0)
+ printf (_("%3ud %2uh %2um %2lus server runtime\n"),
+ ndays, nhours, nmins, diff);
+ else if (nhours != 0)
+ printf (_(" %2uh %2um %2lus server runtime\n"), nhours, nmins, diff);
+ else if (nmins != 0)
+ printf (_(" %2um %2lus server runtime\n"), nmins, diff);
+ else
+ printf (_(" %2lus server runtime\n"), diff);
+
+ printf (_("%15d current number of threads\n"
+ "%15d maximum number of threads\n"
+ "%15lu number of times clients had to wait\n"
+ "%15s paranoia mode enabled\n"
+ "%15lu restart internal\n"
+ "%15u reload count\n"),
+ data.nthreads, data.max_nthreads, data.client_queued,
+ data.paranoia ? yesstr : nostr,
+ (unsigned long int) data.restart_interval, data.reload_count);
+
+ for (i = 0; i < lastdb; ++i)
+ {
+ unsigned long int hit = data.dbs[i].poshit + data.dbs[i].neghit;
+ unsigned long int all = hit + data.dbs[i].posmiss + data.dbs[i].negmiss;
+ const char *enabled = data.dbs[i].enabled ? yesstr : nostr;
+ const char *check_file = data.dbs[i].check_file ? yesstr : nostr;
+ const char *shared = data.dbs[i].shared ? yesstr : nostr;
+ const char *persistent = data.dbs[i].persistent ? yesstr : nostr;
+
+ if (enabled[0] == '\0')
+ /* The locale does not provide this information so we have to
+ translate it ourself. Since we should avoid short translation
+ terms we artifically increase the length. */
+ enabled = data.dbs[i].enabled ? yesstr : nostr;
+ if (check_file[0] == '\0')
+ check_file = data.dbs[i].check_file ? yesstr : nostr;
+ if (shared[0] == '\0')
+ shared = data.dbs[i].shared ? yesstr : nostr;
+ if (persistent[0] == '\0')
+ persistent = data.dbs[i].persistent ? yesstr : nostr;
+
+ if (all == 0)
+ /* If nothing happened so far report a 0% hit rate. */
+ all = 1;
+
+ printf (_("\n%s cache:\n\n"
+ "%15s cache is enabled\n"
+ "%15s cache is persistent\n"
+ "%15s cache is shared\n"
+ "%15zu suggested size\n"
+ "%15zu total data pool size\n"
+ "%15zu used data pool size\n"
+ "%15lu seconds time to live for positive entries\n"
+ "%15lu seconds time to live for negative entries\n"
+ "%15" PRIuMAX " cache hits on positive entries\n"
+ "%15" PRIuMAX " cache hits on negative entries\n"
+ "%15" PRIuMAX " cache misses on positive entries\n"
+ "%15" PRIuMAX " cache misses on negative entries\n"
+ "%15lu%% cache hit rate\n"
+ "%15zu current number of cached values\n"
+ "%15zu maximum number of cached values\n"
+ "%15zu maximum chain length searched\n"
+ "%15" PRIuMAX " number of delays on rdlock\n"
+ "%15" PRIuMAX " number of delays on wrlock\n"
+ "%15" PRIuMAX " memory allocations failed\n"
+ "%15s check /etc/%s for changes\n"),
+ dbnames[i], enabled, persistent, shared,
+ data.dbs[i].module,
+ data.dbs[i].datasize, data.dbs[i].dataused,
+ data.dbs[i].postimeout, data.dbs[i].negtimeout,
+ data.dbs[i].poshit, data.dbs[i].neghit,
+ data.dbs[i].posmiss, data.dbs[i].negmiss,
+ (100 * hit) / all,
+ data.dbs[i].nentries, data.dbs[i].maxnentries,
+ data.dbs[i].maxnsearched,
+ data.dbs[i].rdlockdelayed,
+ data.dbs[i].wrlockdelayed,
+ data.dbs[i].addfailed, check_file, dbnames[i]);
+ }
+
+ if (selinux_enabled)
+ nscd_avc_print_stats (&data.cstats);
+
+ close (fd);
+
+ exit (0);
+}
diff --git a/REORG.TODO/nscd/pwdcache.c b/REORG.TODO/nscd/pwdcache.c
new file mode 100644
index 0000000000..721f4c617b
--- /dev/null
+++ b/REORG.TODO/nscd/pwdcache.c
@@ -0,0 +1,552 @@
+/* Cache handling for passwd lookup.
+ Copyright (C) 1998-2017 Free Software Foundation, Inc.
+ This file is part of the GNU C Library.
+ Contributed by Ulrich Drepper <drepper@cygnus.com>, 1998.
+
+ 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 <error.h>
+#include <libintl.h>
+#include <pwd.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+#include <sys/mman.h>
+#include <sys/socket.h>
+#include <stackinfo.h>
+
+#include "nscd.h"
+#include "dbg_log.h"
+#ifdef HAVE_SENDFILE
+# include <kernel-features.h>
+#endif
+
+/* This is the standard reply in case the service is disabled. */
+static const pw_response_header disabled =
+{
+ .version = NSCD_VERSION,
+ .found = -1,
+ .pw_name_len = 0,
+ .pw_passwd_len = 0,
+ .pw_uid = -1,
+ .pw_gid = -1,
+ .pw_gecos_len = 0,
+ .pw_dir_len = 0,
+ .pw_shell_len = 0
+};
+
+/* This is the struct describing how to write this record. */
+const struct iovec pwd_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 pw_response_header notfound =
+{
+ .version = NSCD_VERSION,
+ .found = 0,
+ .pw_name_len = 0,
+ .pw_passwd_len = 0,
+ .pw_uid = -1,
+ .pw_gid = -1,
+ .pw_gecos_len = 0,
+ .pw_dir_len = 0,
+ .pw_shell_len = 0
+};
+
+
+static time_t
+cache_addpw (struct database_dyn *db, int fd, request_header *req,
+ const void *key, struct passwd *pwd, uid_t owner,
+ struct hashentry *const he, struct datahead *dh, int errval)
+{
+ bool all_written = true;
+ ssize_t total;
+ time_t t = time (NULL);
+
+ /* We allocate all data in one memory block: the iov vector,
+ the response header and the dataset itself. */
+ struct dataset
+ {
+ struct datahead head;
+ pw_response_header resp;
+ char strdata[0];
+ } *dataset;
+
+ assert (offsetof (struct dataset, resp) == offsetof (struct datahead, data));
+
+ time_t timeout = MAX_TIMEOUT_VALUE;
+ if (pwd == NULL)
+ {
+ if (he != NULL && errval == 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 = t + db->postimeout;
+
+ total = 0;
+ }
+ 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, &notfound, total,
+ MSG_NOSIGNAL)) != total)
+ all_written = false;
+
+ /* If we have a transient error or cannot permanently store
+ the result, so be it. */
+ if (errno == EAGAIN || __builtin_expect (db->negtimeout == 0, 0))
+ {
+ /* Mark the old entry as obsolete. */
+ if (dh != NULL)
+ dh->usable = false;
+ }
+ 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, &notfound, total);
+
+ /* Copy the key data. */
+ char *key_copy = memcpy (dataset->strdata, key, req->key_len);
+
+ /* 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 (struct dataset) + req->key_len, MS_ASYNC);
+ }
+
+ (void) cache_add (req->type, key_copy, req->key_len,
+ &dataset->head, true, db, owner, he == NULL);
+
+ pthread_rwlock_unlock (&db->lock);
+
+ /* Mark the old entry as obsolete. */
+ if (dh != NULL)
+ dh->usable = false;
+ }
+ }
+ }
+ else
+ {
+ /* Determine the I/O structure. */
+ size_t pw_name_len = strlen (pwd->pw_name) + 1;
+ size_t pw_passwd_len = strlen (pwd->pw_passwd) + 1;
+ size_t pw_gecos_len = strlen (pwd->pw_gecos) + 1;
+ size_t pw_dir_len = strlen (pwd->pw_dir) + 1;
+ size_t pw_shell_len = strlen (pwd->pw_shell) + 1;
+ char *cp;
+ const size_t key_len = strlen (key);
+ const size_t buf_len = 3 * sizeof (pwd->pw_uid) + key_len + 1;
+ char *buf = alloca (buf_len);
+ ssize_t n;
+
+ /* We need this to insert the `byuid' entry. */
+ int key_offset;
+ n = snprintf (buf, buf_len, "%d%c%n%s", pwd->pw_uid, '\0',
+ &key_offset, (char *) key) + 1;
+
+ total = (offsetof (struct dataset, strdata)
+ + pw_name_len + pw_passwd_len
+ + pw_gecos_len + pw_dir_len + pw_shell_len);
+
+ /* If we refill the cache, first assume the reconrd did not
+ change. Allocate memory on the cache since it is likely
+ discarded anyway. If it turns out to be necessary to have a
+ new record we can still allocate real memory. */
+ bool alloca_used = false;
+ dataset = NULL;
+
+ if (he == NULL)
+ {
+ /* Prevent an INVALIDATE request from pruning the data between
+ the two calls to cache_add. */
+ if (db->propagate)
+ pthread_mutex_lock (&db->prune_run_lock);
+ dataset = (struct dataset *) mempool_alloc (db, total + n, 1);
+ }
+
+ if (dataset == NULL)
+ {
+ if (he == NULL && db->propagate)
+ pthread_mutex_unlock (&db->prune_run_lock);
+
+ /* 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 + n);
+
+ /* We cannot add this record to the permanent database. */
+ alloca_used = true;
+ }
+
+ timeout = datahead_init_pos (&dataset->head, total + n,
+ total - offsetof (struct dataset, resp),
+ he == NULL ? 0 : dh->nreloads + 1,
+ db->postimeout);
+
+ dataset->resp.version = NSCD_VERSION;
+ dataset->resp.found = 1;
+ dataset->resp.pw_name_len = pw_name_len;
+ dataset->resp.pw_passwd_len = pw_passwd_len;
+ dataset->resp.pw_uid = pwd->pw_uid;
+ dataset->resp.pw_gid = pwd->pw_gid;
+ dataset->resp.pw_gecos_len = pw_gecos_len;
+ dataset->resp.pw_dir_len = pw_dir_len;
+ dataset->resp.pw_shell_len = pw_shell_len;
+
+ cp = dataset->strdata;
+
+ /* Copy the strings over into the buffer. */
+ cp = mempcpy (cp, pwd->pw_name, pw_name_len);
+ cp = mempcpy (cp, pwd->pw_passwd, pw_passwd_len);
+ cp = mempcpy (cp, pwd->pw_gecos, pw_gecos_len);
+ cp = mempcpy (cp, pwd->pw_dir, pw_dir_len);
+ cp = mempcpy (cp, pwd->pw_shell, pw_shell_len);
+
+ /* Finally the stringified UID value. */
+ memcpy (cp, buf, n);
+ char *key_copy = cp + key_offset;
+ assert (key_copy == (char *) rawmemchr (cp, '\0') + 1);
+
+ assert (cp == dataset->strdata + total - offsetof (struct dataset,
+ strdata));
+
+ /* 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->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 + n, 1);
+ if (newp != NULL)
+ {
+ /* Adjust pointer into the memory block. */
+ cp = (char *) newp + (cp - (char *) dataset);
+ key_copy = (char *) newp + (key_copy - (char *) dataset);
+
+ dataset = memcpy (newp, dataset, total + n);
+ 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));
+ ssize_t written = sendfileall (fd, db->wr_fd,
+ (char *) &dataset->resp
+ - (char *) db->head,
+ dataset->head.recsize);
+ if (written != dataset->head.recsize)
+ {
+# ifndef __ASSUME_SENDFILE
+ if (written == -1 && errno == ENOSYS)
+ goto use_write;
+# endif
+ all_written = false;
+ }
+ }
+ else
+# ifndef __ASSUME_SENDFILE
+ use_write:
+# endif
+#endif
+ if (writeall (fd, &dataset->resp, dataset->head.recsize)
+ != dataset->head.recsize)
+ all_written = false;
+ }
+
+
+ /* Add the record to the database. But only if it has not been
+ stored on the stack. */
+ if (! 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 + n,
+ MS_ASYNC);
+ }
+
+ /* NB: in the following code we always must add the entry
+ marked with FIRST first. Otherwise we end up with
+ dangling "pointers" in case a latter hash entry cannot be
+ added. */
+ bool first = true;
+
+ /* If the request was by UID, add that entry first. */
+ if (req->type == GETPWBYUID)
+ {
+ if (cache_add (GETPWBYUID, cp, key_offset, &dataset->head, true,
+ db, owner, he == NULL) < 0)
+ goto out;
+
+ first = false;
+ }
+ /* If the key is different from the name add a separate entry. */
+ else if (strcmp (key_copy, dataset->strdata) != 0)
+ {
+ if (cache_add (GETPWBYNAME, key_copy, key_len + 1,
+ &dataset->head, true, db, owner, he == NULL) < 0)
+ goto out;
+
+ first = false;
+ }
+
+ /* We have to add the value for both, byname and byuid. */
+ if ((req->type == GETPWBYNAME || db->propagate)
+ && __builtin_expect (cache_add (GETPWBYNAME, dataset->strdata,
+ pw_name_len, &dataset->head,
+ first, db, owner, he == NULL)
+ == 0, 1))
+ {
+ if (req->type == GETPWBYNAME && db->propagate)
+ (void) cache_add (GETPWBYUID, cp, key_offset, &dataset->head,
+ false, db, owner, false);
+ }
+
+ out:
+ pthread_rwlock_unlock (&db->lock);
+ if (he == NULL && db->propagate)
+ pthread_mutex_unlock (&db->prune_run_lock);
+ }
+ }
+
+ if (__builtin_expect (!all_written, 0) && debug_level > 0)
+ {
+ char buf[256];
+ dbg_log (_("short write in %s: %s"), __FUNCTION__,
+ strerror_r (errno, buf, sizeof (buf)));
+ }
+
+ return timeout;
+}
+
+
+union keytype
+{
+ void *v;
+ uid_t u;
+};
+
+
+static int
+lookup (int type, union keytype key, struct passwd *resultbufp, char *buffer,
+ size_t buflen, struct passwd **pwd)
+{
+ if (type == GETPWBYNAME)
+ return __getpwnam_r (key.v, resultbufp, buffer, buflen, pwd);
+ else
+ return __getpwuid_r (key.u, resultbufp, buffer, buflen, pwd);
+}
+
+
+static time_t
+addpwbyX (struct database_dyn *db, int fd, request_header *req,
+ union keytype key, const char *keystr, uid_t c_uid,
+ struct hashentry *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. */
+ size_t buflen = 1024;
+ char *buffer = (char *) alloca (buflen);
+ struct passwd resultbuf;
+ struct passwd *pwd;
+ bool use_malloc = false;
+ int errval = 0;
+
+ if (__glibc_unlikely (debug_level > 0))
+ {
+ if (he == NULL)
+ dbg_log (_("Haven't found \"%s\" in password cache!"), keystr);
+ else
+ dbg_log (_("Reloading \"%s\" in password cache!"), keystr);
+ }
+
+ while (lookup (req->type, key, &resultbuf, buffer, buflen, &pwd) != 0
+ && (errval = errno) == ERANGE)
+ {
+ errno = 0;
+
+ if (__glibc_unlikely (buflen > 32768))
+ {
+ char *old_buffer = buffer;
+ buflen *= 2;
+ buffer = (char *) realloc (use_malloc ? buffer : NULL, buflen);
+ if (buffer == NULL)
+ {
+ /* We ran out of memory. We cannot do anything but
+ sending a negative response. In reality this should
+ never happen. */
+ pwd = NULL;
+ buffer = old_buffer;
+
+ /* We set the error to indicate this is (possibly) a
+ temporary error and that it does not mean the entry
+ is not available at all. */
+ errval = EAGAIN;
+ break;
+ }
+ use_malloc = true;
+ }
+ else
+ /* Allocate a new buffer on the stack. If possible combine it
+ with the previously allocated buffer. */
+ buffer = (char *) extend_alloca (buffer, buflen, 2 * buflen);
+ }
+
+ /* Add the entry to the cache. */
+ time_t timeout = cache_addpw (db, fd, req, keystr, pwd, c_uid, he, dh,
+ errval);
+
+ if (use_malloc)
+ free (buffer);
+
+ return timeout;
+}
+
+
+void
+addpwbyname (struct database_dyn *db, int fd, request_header *req,
+ void *key, uid_t c_uid)
+{
+ union keytype u = { .v = key };
+
+ addpwbyX (db, fd, req, u, key, c_uid, NULL, NULL);
+}
+
+
+time_t
+readdpwbyname (struct database_dyn *db, struct hashentry *he,
+ struct datahead *dh)
+{
+ request_header req =
+ {
+ .type = GETPWBYNAME,
+ .key_len = he->len
+ };
+ union keytype u = { .v = db->data + he->key };
+
+ return addpwbyX (db, -1, &req, u, db->data + he->key, he->owner, he, dh);
+}
+
+
+void
+addpwbyuid (struct database_dyn *db, int fd, request_header *req,
+ void *key, uid_t c_uid)
+{
+ char *ep;
+ uid_t uid = strtoul ((char *) key, &ep, 10);
+
+ if (*(char *) key == '\0' || *ep != '\0') /* invalid numeric uid */
+ {
+ if (debug_level > 0)
+ dbg_log (_("Invalid numeric uid \"%s\"!"), (char *) key);
+
+ errno = EINVAL;
+ return;
+ }
+
+ union keytype u = { .u = uid };
+
+ addpwbyX (db, fd, req, u, key, c_uid, NULL, NULL);
+}
+
+
+time_t
+readdpwbyuid (struct database_dyn *db, struct hashentry *he,
+ struct datahead *dh)
+{
+ char *ep;
+ uid_t uid = strtoul (db->data + he->key, &ep, 10);
+
+ /* Since the key has been added before it must be OK. */
+ assert (*(db->data + he->key) != '\0' && *ep == '\0');
+
+ request_header req =
+ {
+ .type = GETPWBYUID,
+ .key_len = he->len
+ };
+ union keytype u = { .u = uid };
+
+ return addpwbyX (db, -1, &req, u, db->data + he->key, he->owner, he, dh);
+}
diff --git a/REORG.TODO/nscd/res_hconf.c b/REORG.TODO/nscd/res_hconf.c
new file mode 100644
index 0000000000..14b0e300bc
--- /dev/null
+++ b/REORG.TODO/nscd/res_hconf.c
@@ -0,0 +1,13 @@
+/* Add the include here so that we can redefine __fxprintf. */
+#include <stdio.h>
+
+/* Rename symbols for protected names used in libc itself. */
+#define __ioctl ioctl
+#define __socket socket
+#define __strchrnul strchrnul
+#define __strncasecmp strncasecmp
+
+#define __fxprintf(args...) /* ignore */
+
+
+#include "../resolv/res_hconf.c"
diff --git a/REORG.TODO/nscd/selinux.c b/REORG.TODO/nscd/selinux.c
new file mode 100644
index 0000000000..f7bcd8e4c8
--- /dev/null
+++ b/REORG.TODO/nscd/selinux.c
@@ -0,0 +1,453 @@
+/* SELinux access controls for nscd.
+ Copyright (C) 2004-2017 Free Software Foundation, Inc.
+ This file is part of the GNU C Library.
+ Contributed by Matthew Rickard <mjricka@epoch.ncsc.mil>, 2004.
+
+ The GNU C Library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ The GNU C Library 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
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with the GNU C Library; if not, see
+ <http://www.gnu.org/licenses/>. */
+
+#include "config.h"
+#include <error.h>
+#include <errno.h>
+#include <libintl.h>
+#include <pthread.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <syslog.h>
+#include <unistd.h>
+#include <sys/prctl.h>
+#include <selinux/avc.h>
+#include <selinux/selinux.h>
+#ifdef HAVE_LIBAUDIT
+# include <libaudit.h>
+#endif
+
+#include "dbg_log.h"
+#include "selinux.h"
+
+
+#ifdef HAVE_SELINUX
+/* Global variable to tell if the kernel has SELinux support. */
+int selinux_enabled;
+
+/* Define mappings of request type to AVC permission name. */
+static const char *perms[LASTREQ] =
+{
+ [GETPWBYNAME] = "getpwd",
+ [GETPWBYUID] = "getpwd",
+ [GETGRBYNAME] = "getgrp",
+ [GETGRBYGID] = "getgrp",
+ [GETHOSTBYNAME] = "gethost",
+ [GETHOSTBYNAMEv6] = "gethost",
+ [GETHOSTBYADDR] = "gethost",
+ [GETHOSTBYADDRv6] = "gethost",
+ [SHUTDOWN] = "admin",
+ [GETSTAT] = "getstat",
+ [INVALIDATE] = "admin",
+ [GETFDPW] = "shmempwd",
+ [GETFDGR] = "shmemgrp",
+ [GETFDHST] = "shmemhost",
+ [GETAI] = "gethost",
+ [INITGROUPS] = "getgrp",
+ [GETSERVBYNAME] = "getserv",
+ [GETSERVBYPORT] = "getserv",
+ [GETFDSERV] = "shmemserv",
+ [GETNETGRENT] = "getnetgrp",
+ [INNETGR] = "getnetgrp",
+ [GETFDNETGR] = "shmemnetgrp",
+};
+
+/* Store an entry ref to speed AVC decisions. */
+static struct avc_entry_ref aeref;
+
+/* Thread to listen for SELinux status changes via netlink. */
+static pthread_t avc_notify_thread;
+
+#ifdef HAVE_LIBAUDIT
+/* Prototype for supporting the audit daemon */
+static void log_callback (const char *fmt, ...);
+#endif
+
+/* Prototypes for AVC callback functions. */
+static void *avc_create_thread (void (*run) (void));
+static void avc_stop_thread (void *thread);
+static void *avc_alloc_lock (void);
+static void avc_get_lock (void *lock);
+static void avc_release_lock (void *lock);
+static void avc_free_lock (void *lock);
+
+/* AVC callback structures for use in avc_init. */
+static const struct avc_log_callback log_cb =
+{
+#ifdef HAVE_LIBAUDIT
+ .func_log = log_callback,
+#else
+ .func_log = dbg_log,
+#endif
+ .func_audit = NULL
+};
+static const struct avc_thread_callback thread_cb =
+{
+ .func_create_thread = avc_create_thread,
+ .func_stop_thread = avc_stop_thread
+};
+static const struct avc_lock_callback lock_cb =
+{
+ .func_alloc_lock = avc_alloc_lock,
+ .func_get_lock = avc_get_lock,
+ .func_release_lock = avc_release_lock,
+ .func_free_lock = avc_free_lock
+};
+
+#ifdef HAVE_LIBAUDIT
+/* The audit system's netlink socket descriptor */
+static int audit_fd = -1;
+
+/* When an avc denial occurs, log it to audit system */
+static void
+log_callback (const char *fmt, ...)
+{
+ if (audit_fd >= 0)
+ {
+ va_list ap;
+ va_start (ap, fmt);
+
+ char *buf;
+ int e = vasprintf (&buf, fmt, ap);
+ if (e < 0)
+ {
+ buf = alloca (BUFSIZ);
+ vsnprintf (buf, BUFSIZ, fmt, ap);
+ }
+
+ /* FIXME: need to attribute this to real user, using getuid for now */
+ audit_log_user_avc_message (audit_fd, AUDIT_USER_AVC, buf, NULL, NULL,
+ NULL, getuid ());
+
+ if (e >= 0)
+ free (buf);
+
+ va_end (ap);
+ }
+}
+
+/* Initialize the connection to the audit system */
+static void
+audit_init (void)
+{
+ audit_fd = audit_open ();
+ if (audit_fd < 0
+ /* If kernel doesn't support audit, bail out */
+ && errno != EINVAL && errno != EPROTONOSUPPORT && errno != EAFNOSUPPORT)
+ dbg_log (_("Failed opening connection to the audit subsystem: %m"));
+}
+
+
+# ifdef HAVE_LIBCAP
+static const cap_value_t new_cap_list[] =
+ { CAP_AUDIT_WRITE };
+# define nnew_cap_list (sizeof (new_cap_list) / sizeof (new_cap_list[0]))
+static const cap_value_t tmp_cap_list[] =
+ { CAP_AUDIT_WRITE, CAP_SETUID, CAP_SETGID };
+# define ntmp_cap_list (sizeof (tmp_cap_list) / sizeof (tmp_cap_list[0]))
+
+cap_t
+preserve_capabilities (void)
+{
+ if (getuid () != 0)
+ /* Not root, then we cannot preserve anything. */
+ return NULL;
+
+ if (prctl (PR_SET_KEEPCAPS, 1) == -1)
+ {
+ dbg_log (_("Failed to set keep-capabilities"));
+ do_exit (EXIT_FAILURE, errno, _("prctl(KEEPCAPS) failed"));
+ /* NOTREACHED */
+ }
+
+ cap_t tmp_caps = cap_init ();
+ cap_t new_caps = NULL;
+ if (tmp_caps != NULL)
+ new_caps = cap_init ();
+
+ if (tmp_caps == NULL || new_caps == NULL)
+ {
+ if (tmp_caps != NULL)
+ cap_free (tmp_caps);
+
+ dbg_log (_("Failed to initialize drop of capabilities"));
+ do_exit (EXIT_FAILURE, 0, _("cap_init failed"));
+ }
+
+ /* There is no reason why these should not work. */
+ cap_set_flag (new_caps, CAP_PERMITTED, nnew_cap_list,
+ (cap_value_t *) new_cap_list, CAP_SET);
+ cap_set_flag (new_caps, CAP_EFFECTIVE, nnew_cap_list,
+ (cap_value_t *) new_cap_list, CAP_SET);
+
+ cap_set_flag (tmp_caps, CAP_PERMITTED, ntmp_cap_list,
+ (cap_value_t *) tmp_cap_list, CAP_SET);
+ cap_set_flag (tmp_caps, CAP_EFFECTIVE, ntmp_cap_list,
+ (cap_value_t *) tmp_cap_list, CAP_SET);
+
+ int res = cap_set_proc (tmp_caps);
+
+ cap_free (tmp_caps);
+
+ if (__glibc_unlikely (res != 0))
+ {
+ cap_free (new_caps);
+ dbg_log (_("Failed to drop capabilities"));
+ do_exit (EXIT_FAILURE, 0, _("cap_set_proc failed"));
+ }
+
+ return new_caps;
+}
+
+void
+install_real_capabilities (cap_t new_caps)
+{
+ /* If we have no capabilities there is nothing to do here. */
+ if (new_caps == NULL)
+ return;
+
+ if (cap_set_proc (new_caps))
+ {
+ cap_free (new_caps);
+ dbg_log (_("Failed to drop capabilities"));
+ do_exit (EXIT_FAILURE, 0, _("cap_set_proc failed"));
+ /* NOTREACHED */
+ }
+
+ cap_free (new_caps);
+
+ if (prctl (PR_SET_KEEPCAPS, 0) == -1)
+ {
+ dbg_log (_("Failed to unset keep-capabilities"));
+ do_exit (EXIT_FAILURE, errno, _("prctl(KEEPCAPS) failed"));
+ /* NOTREACHED */
+ }
+}
+# endif /* HAVE_LIBCAP */
+#endif /* HAVE_LIBAUDIT */
+
+/* Determine if we are running on an SELinux kernel. Set selinux_enabled
+ to the result. */
+void
+nscd_selinux_enabled (int *selinux_enabled)
+{
+ *selinux_enabled = is_selinux_enabled ();
+ if (*selinux_enabled < 0)
+ {
+ dbg_log (_("Failed to determine if kernel supports SELinux"));
+ do_exit (EXIT_FAILURE, 0, NULL);
+ }
+}
+
+
+/* Create thread for AVC netlink notification. */
+static void *
+avc_create_thread (void (*run) (void))
+{
+ int rc;
+
+ rc =
+ pthread_create (&avc_notify_thread, NULL, (void *(*) (void *)) run, NULL);
+ if (rc != 0)
+ do_exit (EXIT_FAILURE, rc, _("Failed to start AVC thread"));
+
+ return &avc_notify_thread;
+}
+
+
+/* Stop AVC netlink thread. */
+static void
+avc_stop_thread (void *thread)
+{
+ pthread_cancel (*(pthread_t *) thread);
+}
+
+
+/* Allocate a new AVC lock. */
+static void *
+avc_alloc_lock (void)
+{
+ pthread_mutex_t *avc_mutex;
+
+ avc_mutex = malloc (sizeof (pthread_mutex_t));
+ if (avc_mutex == NULL)
+ do_exit (EXIT_FAILURE, errno, _("Failed to create AVC lock"));
+ pthread_mutex_init (avc_mutex, NULL);
+
+ return avc_mutex;
+}
+
+
+/* Acquire an AVC lock. */
+static void
+avc_get_lock (void *lock)
+{
+ pthread_mutex_lock (lock);
+}
+
+
+/* Release an AVC lock. */
+static void
+avc_release_lock (void *lock)
+{
+ pthread_mutex_unlock (lock);
+}
+
+
+/* Free an AVC lock. */
+static void
+avc_free_lock (void *lock)
+{
+ pthread_mutex_destroy (lock);
+ free (lock);
+}
+
+
+/* Initialize the user space access vector cache (AVC) for NSCD along with
+ log/thread/lock callbacks. */
+void
+nscd_avc_init (void)
+{
+ avc_entry_ref_init (&aeref);
+
+ if (avc_init ("avc", NULL, &log_cb, &thread_cb, &lock_cb) < 0)
+ do_exit (EXIT_FAILURE, errno, _("Failed to start AVC"));
+ else
+ dbg_log (_("Access Vector Cache (AVC) started"));
+#ifdef HAVE_LIBAUDIT
+ audit_init ();
+#endif
+}
+
+
+/* Check the permission from the caller (via getpeercon) to nscd.
+ Returns 0 if access is allowed, 1 if denied, and -1 on error.
+
+ The SELinux policy, enablement, and permission bits are all dynamic and the
+ caching done by glibc is not entirely correct. This nscd support should be
+ rewritten to use selinux_check_permission. A rewrite is risky though and
+ requires some refactoring. Currently we use symbolic mappings instead of
+ compile time constants (which SELinux upstream says are going away), and we
+ use security_deny_unknown to determine what to do if selinux-policy* doesn't
+ have a definition for the the permission or object class we are looking
+ up. */
+int
+nscd_request_avc_has_perm (int fd, request_type req)
+{
+ /* Initialize to NULL so we know what to free in case of failure. */
+ security_context_t scon = NULL;
+ security_context_t tcon = NULL;
+ security_id_t ssid = NULL;
+ security_id_t tsid = NULL;
+ int rc = -1;
+ security_class_t sc_nscd;
+ access_vector_t perm;
+ int avc_deny_unknown;
+
+ /* Check if SELinux denys or allows unknown object classes
+ and permissions. It is 0 if they are allowed, 1 if they
+ are not allowed and -1 on error. */
+ if ((avc_deny_unknown = security_deny_unknown ()) == -1)
+ dbg_log (_("Error querying policy for undefined object classes "
+ "or permissions."));
+
+ /* Get the security class for nscd. If this fails we will likely be
+ unable to do anything unless avc_deny_unknown is 0. */
+ sc_nscd = string_to_security_class ("nscd");
+ if (sc_nscd == 0 && avc_deny_unknown == 1)
+ dbg_log (_("Error getting security class for nscd."));
+
+ /* Convert permission to AVC bits. */
+ perm = string_to_av_perm (sc_nscd, perms[req]);
+ if (perm == 0 && avc_deny_unknown == 1)
+ dbg_log (_("Error translating permission name "
+ "\"%s\" to access vector bit."), perms[req]);
+
+ /* If the nscd security class was not found or perms were not
+ found and AVC does not deny unknown values then allow it. */
+ if ((sc_nscd == 0 || perm == 0) && avc_deny_unknown == 0)
+ return 0;
+
+ if (getpeercon (fd, &scon) < 0)
+ {
+ dbg_log (_("Error getting context of socket peer"));
+ goto out;
+ }
+ if (getcon (&tcon) < 0)
+ {
+ dbg_log (_("Error getting context of nscd"));
+ goto out;
+ }
+ if (avc_context_to_sid (scon, &ssid) < 0
+ || avc_context_to_sid (tcon, &tsid) < 0)
+ {
+ dbg_log (_("Error getting sid from context"));
+ goto out;
+ }
+
+ /* The SELinux API for avc_has_perm conflates access denied and error into
+ the return code -1, while nscd_request_avs_has_perm has distinct error
+ (-1) and denied (1) return codes. We map the avc_has_perm access denied or
+ error into an access denied at the nscd interface level (we do accurately
+ report error for the getpeercon, getcon, and avc_context_to_sid interfaces
+ used above). */
+ rc = avc_has_perm (ssid, tsid, sc_nscd, perm, &aeref, NULL) < 0;
+
+out:
+ if (scon)
+ freecon (scon);
+ if (tcon)
+ freecon (tcon);
+ if (ssid)
+ sidput (ssid);
+ if (tsid)
+ sidput (tsid);
+
+ return rc;
+}
+
+
+/* Wrapper to get AVC statistics. */
+void
+nscd_avc_cache_stats (struct avc_cache_stats *cstats)
+{
+ avc_cache_stats (cstats);
+}
+
+
+/* Print the AVC statistics to stdout. */
+void
+nscd_avc_print_stats (struct avc_cache_stats *cstats)
+{
+ printf (_("\nSELinux AVC Statistics:\n\n"
+ "%15u entry lookups\n"
+ "%15u entry hits\n"
+ "%15u entry misses\n"
+ "%15u entry discards\n"
+ "%15u CAV lookups\n"
+ "%15u CAV hits\n"
+ "%15u CAV probes\n"
+ "%15u CAV misses\n"),
+ cstats->entry_lookups, cstats->entry_hits, cstats->entry_misses,
+ cstats->entry_discards, cstats->cav_lookups, cstats->cav_hits,
+ cstats->cav_probes, cstats->cav_misses);
+}
+
+#endif /* HAVE_SELINUX */
diff --git a/REORG.TODO/nscd/selinux.h b/REORG.TODO/nscd/selinux.h
new file mode 100644
index 0000000000..052d62a6fb
--- /dev/null
+++ b/REORG.TODO/nscd/selinux.h
@@ -0,0 +1,61 @@
+/* Header for nscd SELinux access controls.
+ Copyright (C) 2004-2017 Free Software Foundation, Inc.
+ This file is part of the GNU C Library.
+ Contributed by Matthew Rickard <mjricka@epoch.ncsc.mil>, 2004.
+
+ The GNU C Library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ The GNU C Library 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
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with the GNU C Library; if not, see
+ <http://www.gnu.org/licenses/>. */
+
+#ifndef _SELINUX_H
+#define _SELINUX_H 1
+
+#include "nscd.h"
+#ifdef HAVE_LIBCAP
+# include <sys/capability.h>
+#endif
+
+#ifdef HAVE_SELINUX
+/* Global variable to tell if the kernel has SELinux support. */
+extern int selinux_enabled;
+
+/* Define this for AVC stat usage. */
+struct avc_cache_stats;
+
+/* Initialize the userspace AVC. */
+extern void nscd_avc_init (void);
+/* Determine if we are running on an SELinux kernel. */
+extern void nscd_selinux_enabled (int *selinux_enabled);
+/* Check if the client has permission for the request type. */
+extern int nscd_request_avc_has_perm (int fd, request_type req);
+/* Initialize AVC statistic information. */
+extern void nscd_avc_cache_stats (struct avc_cache_stats *cstats);
+/* Display statistics on AVC usage. */
+extern void nscd_avc_print_stats (struct avc_cache_stats *cstats);
+
+# ifdef HAVE_LIBCAP
+/* Preserve capabilities to connect to the audit daemon. */
+extern cap_t preserve_capabilities (void);
+/* Install final capabilities. */
+extern void install_real_capabilities (cap_t new_caps);
+# endif
+#else
+# define selinux_enabled 0
+# define nscd_avc_init() (void) 0
+# define nscd_selinux_enabled(selinux_enabled) (void) 0
+# define nscd_request_avc_has_perm(fd, req) 0
+# define nscd_avc_cache_stats(cstats) (void) 0
+# define nscd_avc_print_stats(cstats) (void) 0
+#endif /* HAVE_SELINUX */
+
+#endif /* _SELINUX_H */
diff --git a/REORG.TODO/nscd/servicescache.c b/REORG.TODO/nscd/servicescache.c
new file mode 100644
index 0000000000..131ba6ddcc
--- /dev/null
+++ b/REORG.TODO/nscd/servicescache.c
@@ -0,0 +1,474 @@
+/* Cache handling for services lookup.
+ Copyright (C) 2007-2017 Free Software Foundation, Inc.
+ This file is part of the GNU C Library.
+ Contributed by Ulrich Drepper <drepper@drepper.com>, 2007.
+
+ 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 <netdb.h>
+#include <unistd.h>
+#include <stdint.h>
+#include <sys/mman.h>
+#include <kernel-features.h>
+
+#include "nscd.h"
+#include "dbg_log.h"
+
+
+/* This is the standard reply in case the service is disabled. */
+static const serv_response_header disabled =
+{
+ .version = NSCD_VERSION,
+ .found = -1,
+ .s_name_len = 0,
+ .s_proto_len = 0,
+ .s_aliases_cnt = 0,
+ .s_port = -1
+};
+
+/* This is the struct describing how to write this record. */
+const struct iovec serv_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 serv_response_header notfound =
+{
+ .version = NSCD_VERSION,
+ .found = 0,
+ .s_name_len = 0,
+ .s_proto_len = 0,
+ .s_aliases_cnt = 0,
+ .s_port = -1
+};
+
+
+static time_t
+cache_addserv (struct database_dyn *db, int fd, request_header *req,
+ const void *key, struct servent *serv, uid_t owner,
+ struct hashentry *const he, struct datahead *dh, int errval)
+{
+ bool all_written = true;
+ ssize_t total;
+ time_t t = time (NULL);
+
+ /* We allocate all data in one memory block: the iov vector,
+ the response header and the dataset itself. */
+ struct dataset
+ {
+ struct datahead head;
+ serv_response_header resp;
+ char strdata[0];
+ } *dataset;
+
+ assert (offsetof (struct dataset, resp) == offsetof (struct datahead, data));
+
+ time_t timeout = MAX_TIMEOUT_VALUE;
+ if (serv == NULL)
+ {
+ if (he != NULL && errval == 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)
+ /* 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 = t + db->postimeout;
+
+ total = 0;
+ }
+ 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, &notfound, total,
+ MSG_NOSIGNAL)) != total)
+ all_written = false;
+
+ /* If we have a transient error or cannot permanently store
+ the result, so be it. */
+ if (errval == EAGAIN || __builtin_expect (db->negtimeout == 0, 0))
+ {
+ /* Mark the old entry as obsolete. */
+ if (dh != NULL)
+ dh->usable = false;
+ }
+ 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, &notfound, total);
+
+ /* Copy the key data. */
+ memcpy (dataset->strdata, key, req->key_len);
+
+ /* 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 (struct dataset) + req->key_len, MS_ASYNC);
+ }
+
+ (void) cache_add (req->type, &dataset->strdata, req->key_len,
+ &dataset->head, true, db, owner, he == NULL);
+
+ pthread_rwlock_unlock (&db->lock);
+
+ /* Mark the old entry as obsolete. */
+ if (dh != NULL)
+ dh->usable = false;
+ }
+ }
+ }
+ else
+ {
+ /* Determine the I/O structure. */
+ size_t s_name_len = strlen (serv->s_name) + 1;
+ size_t s_proto_len = strlen (serv->s_proto) + 1;
+ uint32_t *s_aliases_len;
+ size_t s_aliases_cnt;
+ char *aliases;
+ char *cp;
+ size_t cnt;
+
+ /* Determine the number of aliases. */
+ s_aliases_cnt = 0;
+ for (cnt = 0; serv->s_aliases[cnt] != NULL; ++cnt)
+ ++s_aliases_cnt;
+ /* Determine the length of all aliases. */
+ s_aliases_len = (uint32_t *) alloca (s_aliases_cnt * sizeof (uint32_t));
+ total = 0;
+ for (cnt = 0; cnt < s_aliases_cnt; ++cnt)
+ {
+ s_aliases_len[cnt] = strlen (serv->s_aliases[cnt]) + 1;
+ total += s_aliases_len[cnt];
+ }
+
+ total += (offsetof (struct dataset, strdata)
+ + s_name_len
+ + s_proto_len
+ + s_aliases_cnt * sizeof (uint32_t));
+
+ /* If we refill the cache, first assume the reconrd did not
+ change. Allocate memory on the cache since it is likely
+ discarded anyway. If it turns out to be necessary to have a
+ new record we can still allocate real memory. */
+ bool alloca_used = false;
+ dataset = NULL;
+
+ if (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;
+ }
+
+ 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.s_name_len = s_name_len;
+ dataset->resp.s_proto_len = s_proto_len;
+ dataset->resp.s_port = serv->s_port;
+ dataset->resp.s_aliases_cnt = s_aliases_cnt;
+
+ cp = dataset->strdata;
+
+ cp = mempcpy (cp, serv->s_name, s_name_len);
+ cp = mempcpy (cp, serv->s_proto, s_proto_len);
+ cp = mempcpy (cp, s_aliases_len, s_aliases_cnt * sizeof (uint32_t));
+
+ /* Then the aliases. */
+ aliases = cp;
+ for (cnt = 0; cnt < s_aliases_cnt; ++cnt)
+ cp = mempcpy (cp, serv->s_aliases[cnt], s_aliases_len[cnt]);
+
+ assert (cp
+ == dataset->strdata + total - offsetof (struct dataset,
+ strdata));
+
+ char *key_copy = memcpy (cp, key, 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 (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->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 (newp != NULL)
+ {
+ /* Adjust pointers into the memory block. */
+ aliases = (char *) newp + (aliases - (char *) dataset);
+ assert (key_copy != NULL);
+ 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 keep the receiver waiting. */
+ 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));
+ ssize_t written = sendfileall (fd, db->wr_fd,
+ (char *) &dataset->resp
+ - (char *) db->head,
+ dataset->head.recsize);
+ if (written != dataset->head.recsize)
+ {
+# ifndef __ASSUME_SENDFILE
+ if (written == -1 && errno == ENOSYS)
+ goto use_write;
+# endif
+ all_written = false;
+ }
+ }
+ else
+# ifndef __ASSUME_SENDFILE
+ use_write:
+# endif
+#endif
+ if (writeall (fd, &dataset->resp, dataset->head.recsize)
+ != dataset->head.recsize)
+ all_written = false;
+ }
+
+ /* Add the record to the database. But only if it has not been
+ stored on the stack. */
+ if (! 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, owner, he == NULL);
+
+ pthread_rwlock_unlock (&db->lock);
+ }
+ }
+
+ if (__builtin_expect (!all_written, 0) && debug_level > 0)
+ {
+ char buf[256];
+ dbg_log (_("short write in %s: %s"), __FUNCTION__,
+ strerror_r (errno, buf, sizeof (buf)));
+ }
+
+ return timeout;
+}
+
+
+static int
+lookup (int type, char *key, struct servent *resultbufp, char *buffer,
+ size_t buflen, struct servent **serv)
+{
+ char *proto = strrchr (key, '/');
+ if (proto != NULL && proto != key)
+ {
+ key = strndupa (key, proto - key);
+ if (proto[1] == '\0')
+ proto = NULL;
+ else
+ ++proto;
+ }
+
+ if (type == GETSERVBYNAME)
+ return __getservbyname_r (key, proto, resultbufp, buffer, buflen, serv);
+
+ assert (type == GETSERVBYPORT);
+ return __getservbyport_r (atol (key), proto, resultbufp, buffer, buflen,
+ serv);
+}
+
+
+static time_t
+addservbyX (struct database_dyn *db, int fd, request_header *req,
+ char *key, uid_t uid, struct hashentry *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. */
+ size_t buflen = 1024;
+ char *buffer = (char *) alloca (buflen);
+ struct servent resultbuf;
+ struct servent *serv;
+ bool use_malloc = false;
+ int errval = 0;
+
+ if (__glibc_unlikely (debug_level > 0))
+ {
+ if (he == NULL)
+ dbg_log (_("Haven't found \"%s\" in services cache!"), key);
+ else
+ dbg_log (_("Reloading \"%s\" in services cache!"), key);
+ }
+
+ while (lookup (req->type, key, &resultbuf, buffer, buflen, &serv) != 0
+ && (errval = errno) == ERANGE)
+ {
+ errno = 0;
+
+ if (__glibc_unlikely (buflen > 32768))
+ {
+ char *old_buffer = buffer;
+ buflen *= 2;
+ buffer = (char *) realloc (use_malloc ? buffer : NULL, buflen);
+ if (buffer == NULL)
+ {
+ /* We ran out of memory. We cannot do anything but
+ sending a negative response. In reality this should
+ never happen. */
+ serv = NULL;
+ buffer = old_buffer;
+
+ /* We set the error to indicate this is (possibly) a
+ temporary error and that it does not mean the entry
+ is not available at all. */
+ errval = EAGAIN;
+ break;
+ }
+ use_malloc = true;
+ }
+ else
+ /* Allocate a new buffer on the stack. If possible combine it
+ with the previously allocated buffer. */
+ buffer = (char *) extend_alloca (buffer, buflen, 2 * buflen);
+ }
+
+ time_t timeout = cache_addserv (db, fd, req, key, serv, uid, he, dh, errval);
+
+ if (use_malloc)
+ free (buffer);
+
+ return timeout;
+}
+
+
+void
+addservbyname (struct database_dyn *db, int fd, request_header *req,
+ void *key, uid_t uid)
+{
+ addservbyX (db, fd, req, key, uid, NULL, NULL);
+}
+
+
+time_t
+readdservbyname (struct database_dyn *db, struct hashentry *he,
+ struct datahead *dh)
+{
+ request_header req =
+ {
+ .type = GETSERVBYNAME,
+ .key_len = he->len
+ };
+
+ return addservbyX (db, -1, &req, db->data + he->key, he->owner, he, dh);
+}
+
+
+void
+addservbyport (struct database_dyn *db, int fd, request_header *req,
+ void *key, uid_t uid)
+{
+ addservbyX (db, fd, req, key, uid, NULL, NULL);
+}
+
+
+time_t
+readdservbyport (struct database_dyn *db, struct hashentry *he,
+ struct datahead *dh)
+{
+ request_header req =
+ {
+ .type = GETSERVBYPORT,
+ .key_len = he->len
+ };
+
+ return addservbyX (db, -1, &req, db->data + he->key, he->owner, he, dh);
+}