x86: Consolidate NPTL/non versions of vfork
[glibc.git] / nscd / netgroupcache.c
blobb3d40e9174c6d3b731d337dca89a4f25b35806d3
1 /* Cache handling for netgroup lookup.
2 Copyright (C) 2011-2014 Free Software Foundation, Inc.
3 This file is part of the GNU C Library.
4 Contributed by Ulrich Drepper <drepper@gmail.com>, 2011.
6 This program is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published
8 by the Free Software Foundation; version 2 of the License, or
9 (at your option) any later version.
11 This program is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
16 You should have received a copy of the GNU General Public License
17 along with this program; if not, see <http://www.gnu.org/licenses/>. */
19 #include <alloca.h>
20 #include <assert.h>
21 #include <errno.h>
22 #include <libintl.h>
23 #include <stdbool.h>
24 #include <unistd.h>
25 #include <sys/mman.h>
27 #include "../inet/netgroup.h"
28 #include "nscd.h"
29 #include "dbg_log.h"
31 #include <kernel-features.h>
34 /* This is the standard reply in case the service is disabled. */
35 static const netgroup_response_header disabled =
37 .version = NSCD_VERSION,
38 .found = -1,
39 .nresults = 0,
40 .result_len = 0
43 /* This is the struct describing how to write this record. */
44 const struct iovec netgroup_iov_disabled =
46 .iov_base = (void *) &disabled,
47 .iov_len = sizeof (disabled)
51 /* This is the standard reply in case we haven't found the dataset. */
52 static const netgroup_response_header notfound =
54 .version = NSCD_VERSION,
55 .found = 0,
56 .nresults = 0,
57 .result_len = 0
61 struct dataset
63 struct datahead head;
64 netgroup_response_header resp;
65 char strdata[0];
68 /* Sends a notfound message and prepares a notfound dataset to write to the
69 cache. Returns true if there was enough memory to allocate the dataset and
70 returns the dataset in DATASETP, total bytes to write in TOTALP and the
71 timeout in TIMEOUTP. KEY_COPY is set to point to the copy of the key in the
72 dataset. */
73 static bool
74 do_notfound (struct database_dyn *db, int fd, request_header *req,
75 const char *key, struct dataset **datasetp, ssize_t *totalp,
76 time_t *timeoutp, char **key_copy)
78 struct dataset *dataset;
79 ssize_t total;
80 time_t timeout;
81 bool cacheable = false;
83 total = sizeof (notfound);
84 timeout = time (NULL) + db->negtimeout;
86 if (fd != -1)
87 TEMP_FAILURE_RETRY (send (fd, &notfound, total, MSG_NOSIGNAL));
89 dataset = mempool_alloc (db, sizeof (struct dataset) + req->key_len, 1);
90 /* If we cannot permanently store the result, so be it. */
91 if (dataset != NULL)
93 timeout = datahead_init_neg (&dataset->head,
94 sizeof (struct dataset) + req->key_len,
95 total, db->negtimeout);
97 /* This is the reply. */
98 memcpy (&dataset->resp, &notfound, total);
100 /* Copy the key data. */
101 memcpy (dataset->strdata, key, req->key_len);
102 *key_copy = dataset->strdata;
104 cacheable = true;
106 *timeoutp = timeout;
107 *totalp = total;
108 *datasetp = dataset;
109 return cacheable;
112 static time_t
113 addgetnetgrentX (struct database_dyn *db, int fd, request_header *req,
114 const char *key, uid_t uid, struct hashentry *he,
115 struct datahead *dh, struct dataset **resultp)
117 if (__glibc_unlikely (debug_level > 0))
119 if (he == NULL)
120 dbg_log (_("Haven't found \"%s\" in netgroup cache!"), key);
121 else
122 dbg_log (_("Reloading \"%s\" in netgroup cache!"), key);
125 static service_user *netgroup_database;
126 time_t timeout;
127 struct dataset *dataset;
128 bool cacheable = false;
129 ssize_t total;
130 bool found = false;
132 char *key_copy = NULL;
133 struct __netgrent data;
134 size_t buflen = MAX (1024, sizeof (*dataset) + req->key_len);
135 size_t buffilled = sizeof (*dataset);
136 char *buffer = NULL;
137 size_t nentries = 0;
138 size_t group_len = strlen (key) + 1;
139 union
141 struct name_list elem;
142 char mem[sizeof (struct name_list) + group_len];
143 } first_needed;
145 if (netgroup_database == NULL
146 && __nss_database_lookup ("netgroup", NULL, NULL, &netgroup_database))
148 /* No such service. */
149 cacheable = do_notfound (db, fd, req, key, &dataset, &total, &timeout,
150 &key_copy);
151 goto writeout;
154 memset (&data, '\0', sizeof (data));
155 buffer = xmalloc (buflen);
156 first_needed.elem.next = &first_needed.elem;
157 memcpy (first_needed.elem.name, key, group_len);
158 data.needed_groups = &first_needed.elem;
160 while (data.needed_groups != NULL)
162 /* Add the next group to the list of those which are known. */
163 struct name_list *this_group = data.needed_groups->next;
164 if (this_group == data.needed_groups)
165 data.needed_groups = NULL;
166 else
167 data.needed_groups->next = this_group->next;
168 this_group->next = data.known_groups;
169 data.known_groups = this_group;
171 union
173 enum nss_status (*f) (const char *, struct __netgrent *);
174 void *ptr;
175 } setfct;
177 service_user *nip = netgroup_database;
178 int no_more = __nss_lookup (&nip, "setnetgrent", NULL, &setfct.ptr);
179 while (!no_more)
181 enum nss_status status
182 = DL_CALL_FCT (*setfct.f, (data.known_groups->name, &data));
184 if (status == NSS_STATUS_SUCCESS)
186 found = true;
187 union
189 enum nss_status (*f) (struct __netgrent *, char *, size_t,
190 int *);
191 void *ptr;
192 } getfct;
193 getfct.ptr = __nss_lookup_function (nip, "getnetgrent_r");
194 if (getfct.f != NULL)
195 while (1)
197 int e;
198 status = getfct.f (&data, buffer + buffilled,
199 buflen - buffilled - req->key_len, &e);
200 if (status == NSS_STATUS_RETURN
201 || status == NSS_STATUS_NOTFOUND)
202 /* This was either the last one for this group or the
203 group was empty. Look at next group if available. */
204 break;
205 if (status == NSS_STATUS_SUCCESS)
207 if (data.type == triple_val)
209 const char *nhost = data.val.triple.host;
210 const char *nuser = data.val.triple.user;
211 const char *ndomain = data.val.triple.domain;
213 size_t hostlen = strlen (nhost ?: "") + 1;
214 size_t userlen = strlen (nuser ?: "") + 1;
215 size_t domainlen = strlen (ndomain ?: "") + 1;
217 if (nhost == NULL || nuser == NULL || ndomain == NULL
218 || nhost > nuser || nuser > ndomain)
220 const char *last = nhost;
221 if (last == NULL
222 || (nuser != NULL && nuser > last))
223 last = nuser;
224 if (last == NULL
225 || (ndomain != NULL && ndomain > last))
226 last = ndomain;
228 size_t bufused
229 = (last == NULL
230 ? buffilled
231 : last + strlen (last) + 1 - buffer);
233 /* We have to make temporary copies. */
234 size_t needed = hostlen + userlen + domainlen;
236 if (buflen - req->key_len - bufused < needed)
238 buflen += MAX (buflen, 2 * needed);
239 /* Save offset in the old buffer. We don't
240 bother with the NULL check here since
241 we'll do that later anyway. */
242 size_t nhostdiff = nhost - buffer;
243 size_t nuserdiff = nuser - buffer;
244 size_t ndomaindiff = ndomain - buffer;
246 char *newbuf = xrealloc (buffer, buflen);
247 /* Fix up the triplet pointers into the new
248 buffer. */
249 nhost = (nhost ? newbuf + nhostdiff
250 : NULL);
251 nuser = (nuser ? newbuf + nuserdiff
252 : NULL);
253 ndomain = (ndomain ? newbuf + ndomaindiff
254 : NULL);
255 buffer = newbuf;
258 nhost = memcpy (buffer + bufused,
259 nhost ?: "", hostlen);
260 nuser = memcpy ((char *) nhost + hostlen,
261 nuser ?: "", userlen);
262 ndomain = memcpy ((char *) nuser + userlen,
263 ndomain ?: "", domainlen);
266 char *wp = buffer + buffilled;
267 wp = memmove (wp, nhost ?: "", hostlen);
268 wp += hostlen;
269 wp = memmove (wp, nuser ?: "", userlen);
270 wp += userlen;
271 wp = memmove (wp, ndomain ?: "", domainlen);
272 wp += domainlen;
273 buffilled = wp - buffer;
274 ++nentries;
276 else
278 /* Check that the group has not been
279 requested before. */
280 struct name_list *runp = data.needed_groups;
281 if (runp != NULL)
282 while (1)
284 if (strcmp (runp->name, data.val.group) == 0)
285 break;
287 runp = runp->next;
288 if (runp == data.needed_groups)
290 runp = NULL;
291 break;
295 if (runp == NULL)
297 runp = data.known_groups;
298 while (runp != NULL)
299 if (strcmp (runp->name, data.val.group) == 0)
300 break;
301 else
302 runp = runp->next;
305 if (runp == NULL)
307 /* A new group is requested. */
308 size_t namelen = strlen (data.val.group) + 1;
309 struct name_list *newg = alloca (sizeof (*newg)
310 + namelen);
311 memcpy (newg->name, data.val.group, namelen);
312 if (data.needed_groups == NULL)
313 data.needed_groups = newg->next = newg;
314 else
316 newg->next = data.needed_groups->next;
317 data.needed_groups->next = newg;
318 data.needed_groups = newg;
323 else if (status == NSS_STATUS_UNAVAIL && e == ERANGE)
325 buflen *= 2;
326 buffer = xrealloc (buffer, buflen);
330 enum nss_status (*endfct) (struct __netgrent *);
331 endfct = __nss_lookup_function (nip, "endnetgrent");
332 if (endfct != NULL)
333 (void) DL_CALL_FCT (*endfct, (&data));
335 break;
338 no_more = __nss_next2 (&nip, "setnetgrent", NULL, &setfct.ptr,
339 status, 0);
343 /* No results. Return a failure and write out a notfound record in the
344 cache. */
345 if (!found)
347 cacheable = do_notfound (db, fd, req, key, &dataset, &total, &timeout,
348 &key_copy);
349 goto writeout;
352 total = buffilled;
354 /* Fill in the dataset. */
355 dataset = (struct dataset *) buffer;
356 timeout = datahead_init_pos (&dataset->head, total + req->key_len,
357 total - offsetof (struct dataset, resp),
358 he == NULL ? 0 : dh->nreloads + 1,
359 db->postimeout);
361 dataset->resp.version = NSCD_VERSION;
362 dataset->resp.found = 1;
363 dataset->resp.nresults = nentries;
364 dataset->resp.result_len = buffilled - sizeof (*dataset);
366 assert (buflen - buffilled >= req->key_len);
367 key_copy = memcpy (buffer + buffilled, key, req->key_len);
368 buffilled += req->key_len;
370 /* Now we can determine whether on refill we have to create a new
371 record or not. */
372 if (he != NULL)
374 assert (fd == -1);
376 if (dataset->head.allocsize == dh->allocsize
377 && dataset->head.recsize == dh->recsize
378 && memcmp (&dataset->resp, dh->data,
379 dh->allocsize - offsetof (struct dataset, resp)) == 0)
381 /* The data has not changed. We will just bump the timeout
382 value. Note that the new record has been allocated on
383 the stack and need not be freed. */
384 dh->timeout = dataset->head.timeout;
385 dh->ttl = dataset->head.ttl;
386 ++dh->nreloads;
387 dataset = (struct dataset *) dh;
389 goto out;
394 struct dataset *newp
395 = (struct dataset *) mempool_alloc (db, total + req->key_len, 1);
396 if (__glibc_likely (newp != NULL))
398 /* Adjust pointer into the memory block. */
399 key_copy = (char *) newp + (key_copy - buffer);
401 dataset = memcpy (newp, dataset, total + req->key_len);
402 cacheable = true;
404 if (he != NULL)
405 /* Mark the old record as obsolete. */
406 dh->usable = false;
410 if (he == NULL && fd != -1)
412 /* We write the dataset before inserting it to the database
413 since while inserting this thread might block and so would
414 unnecessarily let the receiver wait. */
415 writeout:
416 #ifdef HAVE_SENDFILE
417 if (__builtin_expect (db->mmap_used, 1) && cacheable)
419 assert (db->wr_fd != -1);
420 assert ((char *) &dataset->resp > (char *) db->data);
421 assert ((char *) dataset - (char *) db->head + total
422 <= (sizeof (struct database_pers_head)
423 + db->head->module * sizeof (ref_t)
424 + db->head->data_size));
425 # ifndef __ASSUME_SENDFILE
426 ssize_t written =
427 # endif
428 sendfileall (fd, db->wr_fd, (char *) &dataset->resp
429 - (char *) db->head, dataset->head.recsize);
430 # ifndef __ASSUME_SENDFILE
431 if (written == -1 && errno == ENOSYS)
432 goto use_write;
433 # endif
435 else
436 #endif
438 #if defined HAVE_SENDFILE && !defined __ASSUME_SENDFILE
439 use_write:
440 #endif
441 writeall (fd, &dataset->resp, dataset->head.recsize);
445 if (cacheable)
447 /* If necessary, we also propagate the data to disk. */
448 if (db->persistent)
450 // XXX async OK?
451 uintptr_t pval = (uintptr_t) dataset & ~pagesize_m1;
452 msync ((void *) pval,
453 ((uintptr_t) dataset & pagesize_m1) + total + req->key_len,
454 MS_ASYNC);
457 (void) cache_add (req->type, key_copy, req->key_len, &dataset->head,
458 true, db, uid, he == NULL);
460 pthread_rwlock_unlock (&db->lock);
462 /* Mark the old entry as obsolete. */
463 if (dh != NULL)
464 dh->usable = false;
467 out:
468 free (buffer);
470 *resultp = dataset;
472 return timeout;
476 static time_t
477 addinnetgrX (struct database_dyn *db, int fd, request_header *req,
478 char *key, uid_t uid, struct hashentry *he,
479 struct datahead *dh)
481 const char *group = key;
482 key = (char *) rawmemchr (key, '\0') + 1;
483 size_t group_len = key - group - 1;
484 const char *host = *key++ ? key : NULL;
485 if (host != NULL)
486 key = (char *) rawmemchr (key, '\0') + 1;
487 const char *user = *key++ ? key : NULL;
488 if (user != NULL)
489 key = (char *) rawmemchr (key, '\0') + 1;
490 const char *domain = *key++ ? key : NULL;
492 if (__glibc_unlikely (debug_level > 0))
494 if (he == NULL)
495 dbg_log (_("Haven't found \"%s (%s,%s,%s)\" in netgroup cache!"),
496 group, host ?: "", user ?: "", domain ?: "");
497 else
498 dbg_log (_("Reloading \"%s (%s,%s,%s)\" in netgroup cache!"),
499 group, host ?: "", user ?: "", domain ?: "");
502 struct dataset *result = (struct dataset *) cache_search (GETNETGRENT,
503 group, group_len,
504 db, uid);
505 time_t timeout;
506 if (result != NULL)
507 timeout = result->head.timeout;
508 else
510 request_header req_get =
512 .type = GETNETGRENT,
513 .key_len = group_len
515 timeout = addgetnetgrentX (db, -1, &req_get, group, uid, NULL, NULL,
516 &result);
519 struct indataset
521 struct datahead head;
522 innetgroup_response_header resp;
523 } *dataset
524 = (struct indataset *) mempool_alloc (db,
525 sizeof (*dataset) + req->key_len,
527 struct indataset dataset_mem;
528 bool cacheable = true;
529 if (__glibc_unlikely (dataset == NULL))
531 cacheable = false;
532 dataset = &dataset_mem;
535 datahead_init_pos (&dataset->head, sizeof (*dataset) + req->key_len,
536 sizeof (innetgroup_response_header),
537 he == NULL ? 0 : dh->nreloads + 1, result->head.ttl);
538 /* Set the notfound status and timeout based on the result from
539 getnetgrent. */
540 dataset->head.notfound = result->head.notfound;
541 dataset->head.timeout = timeout;
543 dataset->resp.version = NSCD_VERSION;
544 dataset->resp.found = result->resp.found;
545 /* Until we find a matching entry the result is 0. */
546 dataset->resp.result = 0;
548 char *key_copy = memcpy ((char *) (dataset + 1), group, req->key_len);
550 if (dataset->resp.found)
552 const char *triplets = (const char *) (&result->resp + 1);
554 for (nscd_ssize_t i = result->resp.nresults; i > 0; --i)
556 bool success = true;
558 /* For the host, user and domain in each triplet, we assume success
559 if the value is blank because that is how the wildcard entry to
560 match anything is stored in the netgroup cache. */
561 if (host != NULL && *triplets != '\0')
562 success = strcmp (host, triplets) == 0;
563 triplets = (const char *) rawmemchr (triplets, '\0') + 1;
565 if (success && user != NULL && *triplets != '\0')
566 success = strcmp (user, triplets) == 0;
567 triplets = (const char *) rawmemchr (triplets, '\0') + 1;
569 if (success && (domain == NULL || *triplets == '\0'
570 || strcmp (domain, triplets) == 0))
572 dataset->resp.result = 1;
573 break;
575 triplets = (const char *) rawmemchr (triplets, '\0') + 1;
579 if (he != NULL && dh->data[0].innetgroupdata.result == dataset->resp.result)
581 /* The data has not changed. We will just bump the timeout
582 value. Note that the new record has been allocated on
583 the stack and need not be freed. */
584 dh->timeout = timeout;
585 dh->ttl = dataset->head.ttl;
586 ++dh->nreloads;
587 return timeout;
590 if (he == NULL)
592 /* We write the dataset before inserting it to the database
593 since while inserting this thread might block and so would
594 unnecessarily let the receiver wait. */
595 assert (fd != -1);
597 #ifdef HAVE_SENDFILE
598 if (__builtin_expect (db->mmap_used, 1) && cacheable)
600 assert (db->wr_fd != -1);
601 assert ((char *) &dataset->resp > (char *) db->data);
602 assert ((char *) dataset - (char *) db->head + sizeof (*dataset)
603 <= (sizeof (struct database_pers_head)
604 + db->head->module * sizeof (ref_t)
605 + db->head->data_size));
606 # ifndef __ASSUME_SENDFILE
607 ssize_t written =
608 # endif
609 sendfileall (fd, db->wr_fd,
610 (char *) &dataset->resp - (char *) db->head,
611 sizeof (innetgroup_response_header));
612 # ifndef __ASSUME_SENDFILE
613 if (written == -1 && errno == ENOSYS)
614 goto use_write;
615 # endif
617 else
618 #endif
620 #if defined HAVE_SENDFILE && !defined __ASSUME_SENDFILE
621 use_write:
622 #endif
623 writeall (fd, &dataset->resp, sizeof (innetgroup_response_header));
627 if (cacheable)
629 /* If necessary, we also propagate the data to disk. */
630 if (db->persistent)
632 // XXX async OK?
633 uintptr_t pval = (uintptr_t) dataset & ~pagesize_m1;
634 msync ((void *) pval,
635 ((uintptr_t) dataset & pagesize_m1) + sizeof (*dataset)
636 + req->key_len,
637 MS_ASYNC);
640 (void) cache_add (req->type, key_copy, req->key_len, &dataset->head,
641 true, db, uid, he == NULL);
643 pthread_rwlock_unlock (&db->lock);
645 /* Mark the old entry as obsolete. */
646 if (dh != NULL)
647 dh->usable = false;
650 return timeout;
654 void
655 addgetnetgrent (struct database_dyn *db, int fd, request_header *req,
656 void *key, uid_t uid)
658 struct dataset *ignore;
660 addgetnetgrentX (db, fd, req, key, uid, NULL, NULL, &ignore);
664 time_t
665 readdgetnetgrent (struct database_dyn *db, struct hashentry *he,
666 struct datahead *dh)
668 request_header req =
670 .type = GETNETGRENT,
671 .key_len = he->len
673 struct dataset *ignore;
675 return addgetnetgrentX (db, -1, &req, db->data + he->key, he->owner, he, dh,
676 &ignore);
680 void
681 addinnetgr (struct database_dyn *db, int fd, request_header *req,
682 void *key, uid_t uid)
684 addinnetgrX (db, fd, req, key, uid, NULL, NULL);
688 time_t
689 readdinnetgr (struct database_dyn *db, struct hashentry *he,
690 struct datahead *dh)
692 request_header req =
694 .type = INNETGR,
695 .key_len = he->len
698 return addinnetgrX (db, -1, &req, db->data + he->key, he->owner, he, dh);