/* Cache handling for netgroup lookup.
- Copyright (C) 2011-2014 Free Software Foundation, Inc.
+ Copyright (C) 2011-2019 Free Software Foundation, Inc.
This file is part of the GNU C Library.
Contributed by Ulrich Drepper <drepper@gmail.com>, 2011.
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/>. */
+ along with this program; if not, see <https://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>
char strdata[0];
};
+/* Sends a notfound message and prepares a notfound dataset to write to the
+ cache. Returns true if there was enough memory to allocate the dataset and
+ returns the dataset in DATASETP, total bytes to write in TOTALP and the
+ timeout in TIMEOUTP. KEY_COPY is set to point to the copy of the key in the
+ dataset. */
+static bool
+do_notfound (struct database_dyn *db, int fd, request_header *req,
+ const char *key, struct dataset **datasetp, ssize_t *totalp,
+ time_t *timeoutp, char **key_copy)
+{
+ struct dataset *dataset;
+ ssize_t total;
+ time_t timeout;
+ bool cacheable = false;
+
+ total = sizeof (notfound);
+ timeout = time (NULL) + db->negtimeout;
+
+ if (fd != -1)
+ TEMP_FAILURE_RETRY (send (fd, ¬found, total, MSG_NOSIGNAL));
+
+ dataset = mempool_alloc (db, sizeof (struct dataset) + req->key_len, 1);
+ /* If we cannot permanently store the result, so be it. */
+ if (dataset != NULL)
+ {
+ timeout = datahead_init_neg (&dataset->head,
+ sizeof (struct dataset) + req->key_len,
+ total, db->negtimeout);
+
+ /* This is the reply. */
+ memcpy (&dataset->resp, ¬found, total);
+
+ /* Copy the key data. */
+ memcpy (dataset->strdata, key, req->key_len);
+ *key_copy = dataset->strdata;
+
+ cacheable = true;
+ }
+ *timeoutp = timeout;
+ *totalp = total;
+ *datasetp = dataset;
+ return cacheable;
+}
static time_t
addgetnetgrentX (struct database_dyn *db, int fd, request_header *req,
const char *key, uid_t uid, struct hashentry *he,
- struct datahead *dh, struct dataset **resultp)
+ struct datahead *dh, struct dataset **resultp,
+ void **tofreep)
{
- if (__builtin_expect (debug_level > 0, 0))
+ if (__glibc_unlikely (debug_level > 0))
{
if (he == NULL)
dbg_log (_("Haven't found \"%s\" in netgroup cache!"), key);
struct dataset *dataset;
bool cacheable = false;
ssize_t total;
+ bool found = false;
char *key_copy = NULL;
struct __netgrent data;
size_t buffilled = sizeof (*dataset);
char *buffer = NULL;
size_t nentries = 0;
- bool use_malloc = false;
size_t group_len = strlen (key) + 1;
- union
- {
- struct name_list elem;
- char mem[sizeof (struct name_list) + group_len];
- } first_needed;
+ struct name_list *first_needed
+ = alloca (sizeof (struct name_list) + group_len);
+ *tofreep = NULL;
if (netgroup_database == NULL
- && __nss_database_lookup ("netgroup", NULL, NULL, &netgroup_database))
+ && __nss_database_lookup2 ("netgroup", NULL, NULL, &netgroup_database))
{
/* No such service. */
- total = sizeof (notfound);
- timeout = time (NULL) + db->negtimeout;
-
- if (fd != -1)
- TEMP_FAILURE_RETRY (send (fd, ¬found, total, MSG_NOSIGNAL));
-
- dataset = mempool_alloc (db, sizeof (struct dataset) + req->key_len, 1);
- /* If we cannot permanently store the result, so be it. */
- if (dataset != NULL)
- {
- dataset->head.allocsize = sizeof (struct dataset) + req->key_len;
- dataset->head.recsize = total;
- dataset->head.notfound = true;
- dataset->head.nreloads = 0;
- dataset->head.usable = true;
-
- /* Compute the timeout time. */
- timeout = dataset->head.timeout = time (NULL) + db->negtimeout;
- dataset->head.ttl = db->negtimeout;
-
- /* This is the reply. */
- memcpy (&dataset->resp, ¬found, total);
-
- /* Copy the key data. */
- memcpy (dataset->strdata, key, req->key_len);
-
- cacheable = true;
- }
-
+ cacheable = do_notfound (db, fd, req, key, &dataset, &total, &timeout,
+ &key_copy);
goto writeout;
}
memset (&data, '\0', sizeof (data));
- buffer = alloca (buflen);
- first_needed.elem.next = &first_needed.elem;
- memcpy (first_needed.elem.name, key, group_len);
- data.needed_groups = &first_needed.elem;
+ buffer = xmalloc (buflen);
+ *tofreep = buffer;
+ first_needed->next = first_needed;
+ memcpy (first_needed->name, key, group_len);
+ data.needed_groups = first_needed;
while (data.needed_groups != NULL)
{
if (status == NSS_STATUS_SUCCESS)
{
+ found = true;
union
{
enum nss_status (*f) (struct __netgrent *, char *, size_t,
{
int e;
status = getfct.f (&data, buffer + buffilled,
- buflen - buffilled, &e);
- if (status == NSS_STATUS_RETURN)
- /* This was the last one for this group. Look
- at next group if available. */
- break;
+ buflen - buffilled - req->key_len, &e);
if (status == NSS_STATUS_SUCCESS)
{
if (data.type == triple_val)
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)
{
: last + strlen (last) + 1 - buffer);
/* We have to make temporary copies. */
- size_t hostlen = strlen (nhost ?: "") + 1;
- size_t userlen = strlen (nuser ?: "") + 1;
- size_t domainlen = strlen (ndomain ?: "") + 1;
size_t needed = hostlen + userlen + domainlen;
if (buflen - req->key_len - bufused < needed)
{
- size_t newsize = MAX (2 * buflen,
- buflen + 2 * needed);
- if (use_malloc || newsize > 1024 * 1024)
- {
- buflen = newsize;
- char *newbuf = xrealloc (use_malloc
- ? buffer
- : NULL,
- buflen);
-
- buffer = newbuf;
- use_malloc = true;
- }
- else
- extend_alloca (buffer, buflen, newsize);
+ 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,
}
char *wp = buffer + buffilled;
- wp = stpcpy (wp, nhost) + 1;
- wp = stpcpy (wp, nuser) + 1;
- wp = stpcpy (wp, ndomain) + 1;
+ 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 if (status == NSS_STATUS_UNAVAIL && e == ERANGE)
+ else if (status == NSS_STATUS_TRYAGAIN && e == ERANGE)
{
- size_t newsize = 2 * buflen;
- if (use_malloc || newsize > 1024 * 1024)
- {
- buflen = newsize;
- char *newbuf = xrealloc (use_malloc
- ? buffer : NULL, buflen);
-
- buffer = newbuf;
- use_malloc = true;
- }
- else
- extend_alloca (buffer, buflen, newsize);
+ 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 *);
}
}
+ /* 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;
- dataset->head.allocsize = total + req->key_len;
- dataset->head.recsize = total - offsetof (struct dataset, resp);
- dataset->head.notfound = false;
- dataset->head.nreloads = he == NULL ? 0 : (dh->nreloads + 1);
- dataset->head.usable = true;
- dataset->head.ttl = db->postimeout;
- timeout = dataset->head.timeout = time (NULL) + dataset->head.ttl;
+ 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;
{
struct dataset *newp
= (struct dataset *) mempool_alloc (db, total + req->key_len, 1);
- if (__builtin_expect (newp != NULL, 1))
+ if (__glibc_likely (newp != NULL))
{
/* Adjust pointer into the memory block. */
key_copy = (char *) newp + (key_copy - buffer);
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);
- }
+ writeall (fd, &dataset->resp, dataset->head.recsize);
}
if (cacheable)
}
out:
- if (use_malloc)
- free (buffer);
-
*resultp = dataset;
return timeout;
{
const char *group = key;
key = (char *) rawmemchr (key, '\0') + 1;
- size_t group_len = key - group - 1;
+ size_t group_len = key - group;
const char *host = *key++ ? key : NULL;
if (host != NULL)
key = (char *) rawmemchr (key, '\0') + 1;
key = (char *) rawmemchr (key, '\0') + 1;
const char *domain = *key++ ? key : NULL;
- if (__builtin_expect (debug_level > 0, 0))
+ if (__glibc_unlikely (debug_level > 0))
{
if (he == NULL)
dbg_log (_("Haven't found \"%s (%s,%s,%s)\" in netgroup cache!"),
group, group_len,
db, uid);
time_t timeout;
+ void *tofree;
if (result != NULL)
- timeout = result->head.timeout;
+ {
+ timeout = result->head.timeout;
+ tofree = NULL;
+ }
else
{
request_header req_get =
.key_len = group_len
};
timeout = addgetnetgrentX (db, -1, &req_get, group, uid, NULL, NULL,
- &result);
+ &result, &tofree);
}
struct indataset
1);
struct indataset dataset_mem;
bool cacheable = true;
- if (__builtin_expect (dataset == NULL, 0))
+ if (__glibc_unlikely (dataset == NULL))
{
cacheable = false;
dataset = &dataset_mem;
}
- dataset->head.allocsize = sizeof (*dataset) + req->key_len;
- dataset->head.recsize = sizeof (innetgroup_response_header);
+ 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.nreloads = he == NULL ? 0 : (dh->nreloads + 1);
- dataset->head.usable = true;
- dataset->head.ttl = result->head.ttl;
dataset->head.timeout = timeout;
dataset->resp.version = NSCD_VERSION;
{
bool success = true;
- if (host != NULL)
+ /* 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)
+ if (success && user != NULL && *triplets != '\0')
success = strcmp (user, triplets) == 0;
triplets = (const char *) rawmemchr (triplets, '\0') + 1;
- if (success && (domain == NULL || strcmp (domain, triplets) == 0))
+ if (success && (domain == NULL || *triplets == '\0'
+ || strcmp (domain, triplets) == 0))
{
dataset->resp.result = 1;
break;
dh->timeout = timeout;
dh->ttl = dataset->head.ttl;
++dh->nreloads;
- return timeout;
+ if (cacheable)
+ pthread_rwlock_unlock (&db->lock);
+ goto out;
}
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);
+ 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));
- }
+ writeall (fd, &dataset->resp, sizeof (innetgroup_response_header));
}
if (cacheable)
dh->usable = false;
}
+ out:
+ free (tofree);
return timeout;
}
+static time_t
+addgetnetgrentX_ignore (struct database_dyn *db, int fd, request_header *req,
+ const char *key, uid_t uid, struct hashentry *he,
+ struct datahead *dh)
+{
+ struct dataset *ignore;
+ void *tofree;
+ time_t timeout = addgetnetgrentX (db, fd, req, key, uid, he, dh,
+ &ignore, &tofree);
+ free (tofree);
+ 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);
+ addgetnetgrentX_ignore (db, fd, req, key, uid, NULL, NULL);
}
.type = GETNETGRENT,
.key_len = he->len
};
- struct dataset *ignore;
-
- return addgetnetgrentX (db, -1, &req, db->data + he->key, he->owner, he, dh,
- &ignore);
+ return addgetnetgrentX_ignore
+ (db, -1, &req, db->data + he->key, he->owner, he, dh);
}