manual: Sort overview listing by manual order.
[glibc.git] / nscd / netgroupcache.c
blob820d8234b40bf219666c6f4dd5477d68c9e7b625
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 dataset->head.allocsize = sizeof (struct dataset) + req->key_len;
94 dataset->head.recsize = total;
95 dataset->head.notfound = true;
96 dataset->head.nreloads = 0;
97 dataset->head.usable = true;
99 /* Compute the timeout time. */
100 timeout = dataset->head.timeout = time (NULL) + db->negtimeout;
101 dataset->head.ttl = db->negtimeout;
103 /* This is the reply. */
104 memcpy (&dataset->resp, &notfound, total);
106 /* Copy the key data. */
107 memcpy (dataset->strdata, key, req->key_len);
108 *key_copy = dataset->strdata;
110 cacheable = true;
112 *timeoutp = timeout;
113 *totalp = total;
114 *datasetp = dataset;
115 return cacheable;
118 static time_t
119 addgetnetgrentX (struct database_dyn *db, int fd, request_header *req,
120 const char *key, uid_t uid, struct hashentry *he,
121 struct datahead *dh, struct dataset **resultp)
123 if (__glibc_unlikely (debug_level > 0))
125 if (he == NULL)
126 dbg_log (_("Haven't found \"%s\" in netgroup cache!"), key);
127 else
128 dbg_log (_("Reloading \"%s\" in netgroup cache!"), key);
131 static service_user *netgroup_database;
132 time_t timeout;
133 struct dataset *dataset;
134 bool cacheable = false;
135 ssize_t total;
136 bool found = false;
138 char *key_copy = NULL;
139 struct __netgrent data;
140 size_t buflen = MAX (1024, sizeof (*dataset) + req->key_len);
141 size_t buffilled = sizeof (*dataset);
142 char *buffer = NULL;
143 size_t nentries = 0;
144 size_t group_len = strlen (key) + 1;
145 union
147 struct name_list elem;
148 char mem[sizeof (struct name_list) + group_len];
149 } first_needed;
151 if (netgroup_database == NULL
152 && __nss_database_lookup ("netgroup", NULL, NULL, &netgroup_database))
154 /* No such service. */
155 cacheable = do_notfound (db, fd, req, key, &dataset, &total, &timeout,
156 &key_copy);
157 goto writeout;
160 memset (&data, '\0', sizeof (data));
161 buffer = xmalloc (buflen);
162 first_needed.elem.next = &first_needed.elem;
163 memcpy (first_needed.elem.name, key, group_len);
164 data.needed_groups = &first_needed.elem;
166 while (data.needed_groups != NULL)
168 /* Add the next group to the list of those which are known. */
169 struct name_list *this_group = data.needed_groups->next;
170 if (this_group == data.needed_groups)
171 data.needed_groups = NULL;
172 else
173 data.needed_groups->next = this_group->next;
174 this_group->next = data.known_groups;
175 data.known_groups = this_group;
177 union
179 enum nss_status (*f) (const char *, struct __netgrent *);
180 void *ptr;
181 } setfct;
183 service_user *nip = netgroup_database;
184 int no_more = __nss_lookup (&nip, "setnetgrent", NULL, &setfct.ptr);
185 while (!no_more)
187 enum nss_status status
188 = DL_CALL_FCT (*setfct.f, (data.known_groups->name, &data));
190 if (status == NSS_STATUS_SUCCESS)
192 found = true;
193 union
195 enum nss_status (*f) (struct __netgrent *, char *, size_t,
196 int *);
197 void *ptr;
198 } getfct;
199 getfct.ptr = __nss_lookup_function (nip, "getnetgrent_r");
200 if (getfct.f != NULL)
201 while (1)
203 int e;
204 status = getfct.f (&data, buffer + buffilled,
205 buflen - buffilled - req->key_len, &e);
206 if (status == NSS_STATUS_RETURN
207 || status == NSS_STATUS_NOTFOUND)
208 /* This was either the last one for this group or the
209 group was empty. Look at next group if available. */
210 break;
211 if (status == NSS_STATUS_SUCCESS)
213 if (data.type == triple_val)
215 const char *nhost = data.val.triple.host;
216 const char *nuser = data.val.triple.user;
217 const char *ndomain = data.val.triple.domain;
219 size_t hostlen = strlen (nhost ?: "") + 1;
220 size_t userlen = strlen (nuser ?: "") + 1;
221 size_t domainlen = strlen (ndomain ?: "") + 1;
223 if (nhost == NULL || nuser == NULL || ndomain == NULL
224 || nhost > nuser || nuser > ndomain)
226 const char *last = nhost;
227 if (last == NULL
228 || (nuser != NULL && nuser > last))
229 last = nuser;
230 if (last == NULL
231 || (ndomain != NULL && ndomain > last))
232 last = ndomain;
234 size_t bufused
235 = (last == NULL
236 ? buffilled
237 : last + strlen (last) + 1 - buffer);
239 /* We have to make temporary copies. */
240 size_t needed = hostlen + userlen + domainlen;
242 if (buflen - req->key_len - bufused < needed)
244 buflen += MAX (buflen, 2 * needed);
245 /* Save offset in the old buffer. We don't
246 bother with the NULL check here since
247 we'll do that later anyway. */
248 size_t nhostdiff = nhost - buffer;
249 size_t nuserdiff = nuser - buffer;
250 size_t ndomaindiff = ndomain - buffer;
252 char *newbuf = xrealloc (buffer, buflen);
253 /* Fix up the triplet pointers into the new
254 buffer. */
255 nhost = (nhost ? newbuf + nhostdiff
256 : NULL);
257 nuser = (nuser ? newbuf + nuserdiff
258 : NULL);
259 ndomain = (ndomain ? newbuf + ndomaindiff
260 : NULL);
261 buffer = newbuf;
264 nhost = memcpy (buffer + bufused,
265 nhost ?: "", hostlen);
266 nuser = memcpy ((char *) nhost + hostlen,
267 nuser ?: "", userlen);
268 ndomain = memcpy ((char *) nuser + userlen,
269 ndomain ?: "", domainlen);
272 char *wp = buffer + buffilled;
273 wp = memmove (wp, nhost ?: "", hostlen);
274 wp += hostlen;
275 wp = memmove (wp, nuser ?: "", userlen);
276 wp += userlen;
277 wp = memmove (wp, ndomain ?: "", domainlen);
278 wp += domainlen;
279 buffilled = wp - buffer;
280 ++nentries;
282 else
284 /* Check that the group has not been
285 requested before. */
286 struct name_list *runp = data.needed_groups;
287 if (runp != NULL)
288 while (1)
290 if (strcmp (runp->name, data.val.group) == 0)
291 break;
293 runp = runp->next;
294 if (runp == data.needed_groups)
296 runp = NULL;
297 break;
301 if (runp == NULL)
303 runp = data.known_groups;
304 while (runp != NULL)
305 if (strcmp (runp->name, data.val.group) == 0)
306 break;
307 else
308 runp = runp->next;
311 if (runp == NULL)
313 /* A new group is requested. */
314 size_t namelen = strlen (data.val.group) + 1;
315 struct name_list *newg = alloca (sizeof (*newg)
316 + namelen);
317 memcpy (newg->name, data.val.group, namelen);
318 if (data.needed_groups == NULL)
319 data.needed_groups = newg->next = newg;
320 else
322 newg->next = data.needed_groups->next;
323 data.needed_groups->next = newg;
324 data.needed_groups = newg;
329 else if (status == NSS_STATUS_UNAVAIL && e == ERANGE)
331 buflen *= 2;
332 buffer = xrealloc (buffer, buflen);
336 enum nss_status (*endfct) (struct __netgrent *);
337 endfct = __nss_lookup_function (nip, "endnetgrent");
338 if (endfct != NULL)
339 (void) DL_CALL_FCT (*endfct, (&data));
341 break;
344 no_more = __nss_next2 (&nip, "setnetgrent", NULL, &setfct.ptr,
345 status, 0);
349 /* No results. Return a failure and write out a notfound record in the
350 cache. */
351 if (!found)
353 cacheable = do_notfound (db, fd, req, key, &dataset, &total, &timeout,
354 &key_copy);
355 goto writeout;
358 total = buffilled;
360 /* Fill in the dataset. */
361 dataset = (struct dataset *) buffer;
362 dataset->head.allocsize = total + req->key_len;
363 dataset->head.recsize = total - offsetof (struct dataset, resp);
364 dataset->head.notfound = false;
365 dataset->head.nreloads = he == NULL ? 0 : (dh->nreloads + 1);
366 dataset->head.usable = true;
367 dataset->head.ttl = db->postimeout;
368 timeout = dataset->head.timeout = time (NULL) + dataset->head.ttl;
370 dataset->resp.version = NSCD_VERSION;
371 dataset->resp.found = 1;
372 dataset->resp.nresults = nentries;
373 dataset->resp.result_len = buffilled - sizeof (*dataset);
375 assert (buflen - buffilled >= req->key_len);
376 key_copy = memcpy (buffer + buffilled, key, req->key_len);
377 buffilled += req->key_len;
379 /* Now we can determine whether on refill we have to create a new
380 record or not. */
381 if (he != NULL)
383 assert (fd == -1);
385 if (dataset->head.allocsize == dh->allocsize
386 && dataset->head.recsize == dh->recsize
387 && memcmp (&dataset->resp, dh->data,
388 dh->allocsize - offsetof (struct dataset, resp)) == 0)
390 /* The data has not changed. We will just bump the timeout
391 value. Note that the new record has been allocated on
392 the stack and need not be freed. */
393 dh->timeout = dataset->head.timeout;
394 dh->ttl = dataset->head.ttl;
395 ++dh->nreloads;
396 dataset = (struct dataset *) dh;
398 goto out;
403 struct dataset *newp
404 = (struct dataset *) mempool_alloc (db, total + req->key_len, 1);
405 if (__glibc_likely (newp != NULL))
407 /* Adjust pointer into the memory block. */
408 key_copy = (char *) newp + (key_copy - buffer);
410 dataset = memcpy (newp, dataset, total + req->key_len);
411 cacheable = true;
413 if (he != NULL)
414 /* Mark the old record as obsolete. */
415 dh->usable = false;
419 if (he == NULL && fd != -1)
421 /* We write the dataset before inserting it to the database
422 since while inserting this thread might block and so would
423 unnecessarily let the receiver wait. */
424 writeout:
425 #ifdef HAVE_SENDFILE
426 if (__builtin_expect (db->mmap_used, 1) && cacheable)
428 assert (db->wr_fd != -1);
429 assert ((char *) &dataset->resp > (char *) db->data);
430 assert ((char *) dataset - (char *) db->head + total
431 <= (sizeof (struct database_pers_head)
432 + db->head->module * sizeof (ref_t)
433 + db->head->data_size));
434 # ifndef __ASSUME_SENDFILE
435 ssize_t written =
436 # endif
437 sendfileall (fd, db->wr_fd, (char *) &dataset->resp
438 - (char *) db->head, dataset->head.recsize);
439 # ifndef __ASSUME_SENDFILE
440 if (written == -1 && errno == ENOSYS)
441 goto use_write;
442 # endif
444 else
445 #endif
447 #if defined HAVE_SENDFILE && !defined __ASSUME_SENDFILE
448 use_write:
449 #endif
450 writeall (fd, &dataset->resp, dataset->head.recsize);
454 if (cacheable)
456 /* If necessary, we also propagate the data to disk. */
457 if (db->persistent)
459 // XXX async OK?
460 uintptr_t pval = (uintptr_t) dataset & ~pagesize_m1;
461 msync ((void *) pval,
462 ((uintptr_t) dataset & pagesize_m1) + total + req->key_len,
463 MS_ASYNC);
466 (void) cache_add (req->type, key_copy, req->key_len, &dataset->head,
467 true, db, uid, he == NULL);
469 pthread_rwlock_unlock (&db->lock);
471 /* Mark the old entry as obsolete. */
472 if (dh != NULL)
473 dh->usable = false;
476 out:
477 free (buffer);
479 *resultp = dataset;
481 return timeout;
485 static time_t
486 addinnetgrX (struct database_dyn *db, int fd, request_header *req,
487 char *key, uid_t uid, struct hashentry *he,
488 struct datahead *dh)
490 const char *group = key;
491 key = (char *) rawmemchr (key, '\0') + 1;
492 size_t group_len = key - group - 1;
493 const char *host = *key++ ? key : NULL;
494 if (host != NULL)
495 key = (char *) rawmemchr (key, '\0') + 1;
496 const char *user = *key++ ? key : NULL;
497 if (user != NULL)
498 key = (char *) rawmemchr (key, '\0') + 1;
499 const char *domain = *key++ ? key : NULL;
501 if (__glibc_unlikely (debug_level > 0))
503 if (he == NULL)
504 dbg_log (_("Haven't found \"%s (%s,%s,%s)\" in netgroup cache!"),
505 group, host ?: "", user ?: "", domain ?: "");
506 else
507 dbg_log (_("Reloading \"%s (%s,%s,%s)\" in netgroup cache!"),
508 group, host ?: "", user ?: "", domain ?: "");
511 struct dataset *result = (struct dataset *) cache_search (GETNETGRENT,
512 group, group_len,
513 db, uid);
514 time_t timeout;
515 if (result != NULL)
516 timeout = result->head.timeout;
517 else
519 request_header req_get =
521 .type = GETNETGRENT,
522 .key_len = group_len
524 timeout = addgetnetgrentX (db, -1, &req_get, group, uid, NULL, NULL,
525 &result);
528 struct indataset
530 struct datahead head;
531 innetgroup_response_header resp;
532 } *dataset
533 = (struct indataset *) mempool_alloc (db,
534 sizeof (*dataset) + req->key_len,
536 struct indataset dataset_mem;
537 bool cacheable = true;
538 if (__glibc_unlikely (dataset == NULL))
540 cacheable = false;
541 dataset = &dataset_mem;
544 dataset->head.allocsize = sizeof (*dataset) + req->key_len;
545 dataset->head.recsize = sizeof (innetgroup_response_header);
546 dataset->head.notfound = result->head.notfound;
547 dataset->head.nreloads = he == NULL ? 0 : (dh->nreloads + 1);
548 dataset->head.usable = true;
549 dataset->head.ttl = result->head.ttl;
550 dataset->head.timeout = timeout;
552 dataset->resp.version = NSCD_VERSION;
553 dataset->resp.found = result->resp.found;
554 /* Until we find a matching entry the result is 0. */
555 dataset->resp.result = 0;
557 char *key_copy = memcpy ((char *) (dataset + 1), group, req->key_len);
559 if (dataset->resp.found)
561 const char *triplets = (const char *) (&result->resp + 1);
563 for (nscd_ssize_t i = result->resp.nresults; i > 0; --i)
565 bool success = true;
567 /* For the host, user and domain in each triplet, we assume success
568 if the value is blank because that is how the wildcard entry to
569 match anything is stored in the netgroup cache. */
570 if (host != NULL && *triplets != '\0')
571 success = strcmp (host, triplets) == 0;
572 triplets = (const char *) rawmemchr (triplets, '\0') + 1;
574 if (success && user != NULL && *triplets != '\0')
575 success = strcmp (user, triplets) == 0;
576 triplets = (const char *) rawmemchr (triplets, '\0') + 1;
578 if (success && (domain == NULL || *triplets == '\0'
579 || strcmp (domain, triplets) == 0))
581 dataset->resp.result = 1;
582 break;
584 triplets = (const char *) rawmemchr (triplets, '\0') + 1;
588 if (he != NULL && dh->data[0].innetgroupdata.result == dataset->resp.result)
590 /* The data has not changed. We will just bump the timeout
591 value. Note that the new record has been allocated on
592 the stack and need not be freed. */
593 dh->timeout = timeout;
594 dh->ttl = dataset->head.ttl;
595 ++dh->nreloads;
596 return timeout;
599 if (he == NULL)
601 /* We write the dataset before inserting it to the database
602 since while inserting this thread might block and so would
603 unnecessarily let the receiver wait. */
604 assert (fd != -1);
606 #ifdef HAVE_SENDFILE
607 if (__builtin_expect (db->mmap_used, 1) && cacheable)
609 assert (db->wr_fd != -1);
610 assert ((char *) &dataset->resp > (char *) db->data);
611 assert ((char *) dataset - (char *) db->head + sizeof (*dataset)
612 <= (sizeof (struct database_pers_head)
613 + db->head->module * sizeof (ref_t)
614 + db->head->data_size));
615 # ifndef __ASSUME_SENDFILE
616 ssize_t written =
617 # endif
618 sendfileall (fd, db->wr_fd,
619 (char *) &dataset->resp - (char *) db->head,
620 sizeof (innetgroup_response_header));
621 # ifndef __ASSUME_SENDFILE
622 if (written == -1 && errno == ENOSYS)
623 goto use_write;
624 # endif
626 else
627 #endif
629 #if defined HAVE_SENDFILE && !defined __ASSUME_SENDFILE
630 use_write:
631 #endif
632 writeall (fd, &dataset->resp, sizeof (innetgroup_response_header));
636 if (cacheable)
638 /* If necessary, we also propagate the data to disk. */
639 if (db->persistent)
641 // XXX async OK?
642 uintptr_t pval = (uintptr_t) dataset & ~pagesize_m1;
643 msync ((void *) pval,
644 ((uintptr_t) dataset & pagesize_m1) + sizeof (*dataset)
645 + req->key_len,
646 MS_ASYNC);
649 (void) cache_add (req->type, key_copy, req->key_len, &dataset->head,
650 true, db, uid, he == NULL);
652 pthread_rwlock_unlock (&db->lock);
654 /* Mark the old entry as obsolete. */
655 if (dh != NULL)
656 dh->usable = false;
659 return timeout;
663 void
664 addgetnetgrent (struct database_dyn *db, int fd, request_header *req,
665 void *key, uid_t uid)
667 struct dataset *ignore;
669 addgetnetgrentX (db, fd, req, key, uid, NULL, NULL, &ignore);
673 time_t
674 readdgetnetgrent (struct database_dyn *db, struct hashentry *he,
675 struct datahead *dh)
677 request_header req =
679 .type = GETNETGRENT,
680 .key_len = he->len
682 struct dataset *ignore;
684 return addgetnetgrentX (db, -1, &req, db->data + he->key, he->owner, he, dh,
685 &ignore);
689 void
690 addinnetgr (struct database_dyn *db, int fd, request_header *req,
691 void *key, uid_t uid)
693 addinnetgrX (db, fd, req, key, uid, NULL, NULL);
697 time_t
698 readdinnetgr (struct database_dyn *db, struct hashentry *he,
699 struct datahead *dh)
701 request_header req =
703 .type = INNETGR,
704 .key_len = he->len
707 return addinnetgrX (db, -1, &req, db->data + he->key, he->owner, he, dh);