Replace FSF snail mail address with URLs.
[glibc.git] / nscd / netgroupcache.c
blob276e66ff449cf4bf38f420b73d49d751e8885a56
1 /* Cache handling for netgroup lookup.
2 Copyright (C) 2011 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"
30 #ifdef HAVE_SENDFILE
31 # include <kernel-features.h>
32 #endif
35 /* This is the standard reply in case the service is disabled. */
36 static const netgroup_response_header disabled =
38 .version = NSCD_VERSION,
39 .found = -1,
40 .nresults = 0,
41 .result_len = 0
44 /* This is the struct describing how to write this record. */
45 const struct iovec netgroup_iov_disabled =
47 .iov_base = (void *) &disabled,
48 .iov_len = sizeof (disabled)
52 /* This is the standard reply in case we haven't found the dataset. */
53 static const netgroup_response_header notfound =
55 .version = NSCD_VERSION,
56 .found = 0,
57 .nresults = 0,
58 .result_len = 0
62 struct dataset
64 struct datahead head;
65 netgroup_response_header resp;
66 char strdata[0];
70 static time_t
71 addgetnetgrentX (struct database_dyn *db, int fd, request_header *req,
72 const char *key, uid_t uid, struct hashentry *he,
73 struct datahead *dh, struct dataset **resultp)
75 if (__builtin_expect (debug_level > 0, 0))
77 if (he == NULL)
78 dbg_log (_("Haven't found \"%s\" in netgroup cache!"), key);
79 else
80 dbg_log (_("Reloading \"%s\" in netgroup cache!"), key);
83 static service_user *netgroup_database;
84 time_t timeout;
85 struct dataset *dataset;
86 bool cacheable = false;
87 ssize_t total;
89 char *key_copy = NULL;
90 struct __netgrent data;
91 size_t buflen = MAX (1024, sizeof (*dataset) + req->key_len);
92 size_t buffilled = sizeof (*dataset);
93 char *buffer = NULL;
94 size_t nentries = 0;
95 bool use_malloc = false;
96 size_t group_len = strlen (key) + 1;
97 union
99 struct name_list elem;
100 char mem[sizeof (struct name_list) + group_len];
101 } first_needed;
103 if (netgroup_database == NULL
104 && __nss_database_lookup ("netgroup", NULL, NULL, &netgroup_database))
106 /* No such service. */
107 total = sizeof (notfound);
108 timeout = time (NULL) + db->negtimeout;
110 if (fd != -1)
111 TEMP_FAILURE_RETRY (send (fd, &notfound, total, MSG_NOSIGNAL));
113 dataset = mempool_alloc (db, sizeof (struct dataset) + req->key_len, 1);
114 /* If we cannot permanently store the result, so be it. */
115 if (dataset != NULL)
117 dataset->head.allocsize = sizeof (struct dataset) + req->key_len;
118 dataset->head.recsize = total;
119 dataset->head.notfound = true;
120 dataset->head.nreloads = 0;
121 dataset->head.usable = true;
123 /* Compute the timeout time. */
124 timeout = dataset->head.timeout = time (NULL) + db->negtimeout;
125 dataset->head.ttl = db->negtimeout;
127 /* This is the reply. */
128 memcpy (&dataset->resp, &notfound, total);
130 /* Copy the key data. */
131 memcpy (dataset->strdata, key, req->key_len);
133 cacheable = true;
136 goto writeout;
139 memset (&data, '\0', sizeof (data));
140 buffer = alloca (buflen);
141 first_needed.elem.next = &first_needed.elem;
142 memcpy (first_needed.elem.name, key, group_len);
143 data.needed_groups = &first_needed.elem;
145 while (data.needed_groups != NULL)
147 /* Add the next group to the list of those which are known. */
148 struct name_list *this_group = data.needed_groups->next;
149 if (this_group == data.needed_groups)
150 data.needed_groups = NULL;
151 else
152 data.needed_groups->next = this_group->next;
153 this_group->next = data.known_groups;
154 data.known_groups = this_group;
156 union
158 enum nss_status (*f) (const char *, struct __netgrent *);
159 void *ptr;
160 } setfct;
162 service_user *nip = netgroup_database;
163 int no_more = __nss_lookup (&nip, "setnetgrent", NULL, &setfct.ptr);
164 while (!no_more)
166 enum nss_status status
167 = DL_CALL_FCT (*setfct.f, (data.known_groups->name, &data));
169 if (status == NSS_STATUS_SUCCESS)
171 union
173 enum nss_status (*f) (struct __netgrent *, char *, size_t,
174 int *);
175 void *ptr;
176 } getfct;
177 getfct.ptr = __nss_lookup_function (nip, "getnetgrent_r");
178 if (getfct.f != NULL)
179 while (1)
181 int e;
182 status = getfct.f (&data, buffer + buffilled,
183 buflen - buffilled, &e);
184 if (status == NSS_STATUS_RETURN)
185 /* This was the last one for this group. Look
186 at next group if available. */
187 break;
188 if (status == NSS_STATUS_SUCCESS)
190 if (data.type == triple_val)
192 const char *nhost = data.val.triple.host;
193 const char *nuser = data.val.triple.user;
194 const char *ndomain = data.val.triple.domain;
196 if (data.val.triple.host > data.val.triple.user
197 || data.val.triple.user > data.val.triple.domain)
199 const char *last = MAX (nhost,
200 MAX (nuser, ndomain));
201 size_t bufused = (last + strlen (last) + 1
202 - buffer);
204 /* We have to make temporary copies. */
205 size_t hostlen = strlen (nhost) + 1;
206 size_t userlen = strlen (nuser) + 1;
207 size_t domainlen = strlen (ndomain) + 1;
208 size_t needed = hostlen + userlen + domainlen;
210 if (buflen - req->key_len - bufused < needed)
212 size_t newsize = MAX (2 * buflen,
213 buflen + 2 * needed);
214 if (use_malloc || newsize > 1024 * 1024)
216 buflen = newsize;
217 char *newbuf = xrealloc (use_malloc
218 ? buffer
219 : NULL,
220 buflen);
222 buffer = newbuf;
223 use_malloc = true;
225 else
226 extend_alloca (buffer, buflen, newsize);
229 nhost = memcpy (buffer + bufused,
230 nhost, hostlen);
231 nuser = memcpy ((char *) nhost + hostlen,
232 nuser, userlen);
233 ndomain = memcpy ((char *) nuser + userlen,
234 ndomain, domainlen);
237 char *wp = buffer + buffilled;
238 wp = stpcpy (wp, nhost) + 1;
239 wp = stpcpy (wp, nuser) + 1;
240 wp = stpcpy (wp, ndomain) + 1;
241 buffilled = wp - buffer;
242 ++nentries;
244 else
246 /* Check that the group has not been
247 requested before. */
248 struct name_list *runp = data.needed_groups;
249 if (runp != NULL)
250 while (1)
252 if (strcmp (runp->name, data.val.group) == 0)
253 break;
255 runp = runp->next;
256 if (runp == data.needed_groups)
258 runp = NULL;
259 break;
263 if (runp == NULL)
265 runp = data.known_groups;
266 while (runp != NULL)
267 if (strcmp (runp->name, data.val.group) == 0)
268 break;
269 else
270 runp = runp->next;
273 if (runp == NULL)
275 /* A new group is requested. */
276 size_t namelen = strlen (data.val.group) + 1;
277 struct name_list *newg = alloca (sizeof (*newg)
278 + namelen);
279 memcpy (newg->name, data.val.group, namelen);
280 if (data.needed_groups == NULL)
281 data.needed_groups = newg->next = newg;
282 else
284 newg->next = data.needed_groups->next;
285 data.needed_groups->next = newg;
286 data.needed_groups = newg;
291 else if (status == NSS_STATUS_UNAVAIL && e == ERANGE)
293 size_t newsize = 2 * buflen;
294 if (use_malloc || newsize > 1024 * 1024)
296 buflen = newsize;
297 char *newbuf = xrealloc (use_malloc
298 ? buffer : NULL, buflen);
300 buffer = newbuf;
301 use_malloc = true;
303 else
304 extend_alloca (buffer, buflen, newsize);
308 enum nss_status (*endfct) (struct __netgrent *);
309 endfct = __nss_lookup_function (nip, "endnetgrent");
310 if (endfct != NULL)
311 (void) DL_CALL_FCT (*endfct, (&data));
313 break;
316 no_more = __nss_next2 (&nip, "setnetgrent", NULL, &setfct.ptr,
317 status, 0);
321 total = buffilled;
323 /* Fill in the dataset. */
324 dataset = (struct dataset *) buffer;
325 dataset->head.allocsize = total + req->key_len;
326 dataset->head.recsize = total - offsetof (struct dataset, resp);
327 dataset->head.notfound = false;
328 dataset->head.nreloads = he == NULL ? 0 : (dh->nreloads + 1);
329 dataset->head.usable = true;
330 dataset->head.ttl = db->postimeout;
331 timeout = dataset->head.timeout = time (NULL) + dataset->head.ttl;
333 dataset->resp.version = NSCD_VERSION;
334 dataset->resp.found = 1;
335 dataset->resp.nresults = nentries;
336 dataset->resp.result_len = buffilled - sizeof (*dataset);
338 assert (buflen - buffilled >= req->key_len);
339 key_copy = memcpy (buffer + buffilled, key, req->key_len);
340 buffilled += req->key_len;
342 /* Now we can determine whether on refill we have to create a new
343 record or not. */
344 if (he != NULL)
346 assert (fd == -1);
348 if (dataset->head.allocsize == dh->allocsize
349 && dataset->head.recsize == dh->recsize
350 && memcmp (&dataset->resp, dh->data,
351 dh->allocsize - offsetof (struct dataset, resp)) == 0)
353 /* The data has not changed. We will just bump the timeout
354 value. Note that the new record has been allocated on
355 the stack and need not be freed. */
356 dh->timeout = dataset->head.timeout;
357 dh->ttl = dataset->head.ttl;
358 ++dh->nreloads;
359 dataset = (struct dataset *) dh;
361 goto out;
366 struct dataset *newp
367 = (struct dataset *) mempool_alloc (db, total + req->key_len, 1);
368 if (__builtin_expect (newp != NULL, 1))
370 /* Adjust pointer into the memory block. */
371 key_copy = (char *) newp + (key_copy - buffer);
373 dataset = memcpy (newp, dataset, total + req->key_len);
374 cacheable = true;
376 if (he != NULL)
377 /* Mark the old record as obsolete. */
378 dh->usable = false;
382 if (he == NULL && fd != -1)
384 /* We write the dataset before inserting it to the database
385 since while inserting this thread might block and so would
386 unnecessarily let the receiver wait. */
387 writeout:
388 #ifdef HAVE_SENDFILE
389 if (__builtin_expect (db->mmap_used, 1) && cacheable)
391 assert (db->wr_fd != -1);
392 assert ((char *) &dataset->resp > (char *) db->data);
393 assert ((char *) dataset - (char *) db->head + total
394 <= (sizeof (struct database_pers_head)
395 + db->head->module * sizeof (ref_t)
396 + db->head->data_size));
397 # ifndef __ASSUME_SENDFILE
398 ssize_t written =
399 # endif
400 sendfileall (fd, db->wr_fd, (char *) &dataset->resp
401 - (char *) db->head, dataset->head.recsize);
402 # ifndef __ASSUME_SENDFILE
403 if (written == -1 && errno == ENOSYS)
404 goto use_write;
405 # endif
407 else
408 #endif
410 #if defined HAVE_SENDFILE && !defined __ASSUME_SENDFILE
411 use_write:
412 #endif
413 writeall (fd, &dataset->resp, dataset->head.recsize);
417 if (cacheable)
419 /* If necessary, we also propagate the data to disk. */
420 if (db->persistent)
422 // XXX async OK?
423 uintptr_t pval = (uintptr_t) dataset & ~pagesize_m1;
424 msync ((void *) pval,
425 ((uintptr_t) dataset & pagesize_m1) + total + req->key_len,
426 MS_ASYNC);
429 (void) cache_add (req->type, key_copy, req->key_len, &dataset->head,
430 true, db, uid, he == NULL);
432 pthread_rwlock_unlock (&db->lock);
434 /* Mark the old entry as obsolete. */
435 if (dh != NULL)
436 dh->usable = false;
439 out:
440 if (use_malloc)
441 free (buffer);
443 *resultp = dataset;
445 return timeout;
449 static time_t
450 addinnetgrX (struct database_dyn *db, int fd, request_header *req,
451 char *key, uid_t uid, struct hashentry *he,
452 struct datahead *dh)
454 const char *group = key;
455 key = (char *) rawmemchr (key, '\0') + 1;
456 size_t group_len = key - group - 1;
457 const char *host = *key++ ? key : NULL;
458 if (host != NULL)
459 key = (char *) rawmemchr (key, '\0') + 1;
460 const char *user = *key++ ? key : NULL;
461 if (user != NULL)
462 key = (char *) rawmemchr (key, '\0') + 1;
463 const char *domain = *key++ ? key : NULL;
465 if (__builtin_expect (debug_level > 0, 0))
467 if (he == NULL)
468 dbg_log (_("Haven't found \"%s (%s,%s,%s)\" in netgroup cache!"),
469 group, host ?: "", user ?: "", domain ?: "");
470 else
471 dbg_log (_("Reloading \"%s (%s,%s,%s)\" in netgroup cache!"),
472 group, host ?: "", user ?: "", domain ?: "");
475 struct dataset *result = (struct dataset *) cache_search (GETNETGRENT,
476 group, group_len,
477 db, uid);
478 time_t timeout;
479 if (result != NULL)
480 timeout = result->head.timeout;
481 else
483 request_header req_get =
485 .type = GETNETGRENT,
486 .key_len = group_len
488 timeout = addgetnetgrentX (db, -1, &req_get, group, uid, NULL, NULL,
489 &result);
492 struct indataset
494 struct datahead head;
495 innetgroup_response_header resp;
496 } *dataset
497 = (struct indataset *) mempool_alloc (db,
498 sizeof (*dataset) + req->key_len,
500 struct indataset dataset_mem;
501 bool cacheable = true;
502 if (__builtin_expect (dataset == NULL, 0))
504 cacheable = false;
505 dataset = &dataset_mem;
508 dataset->head.allocsize = sizeof (*dataset) + req->key_len;
509 dataset->head.recsize = sizeof (innetgroup_response_header);
510 dataset->head.notfound = result->head.notfound;
511 dataset->head.nreloads = he == NULL ? 0 : (dh->nreloads + 1);
512 dataset->head.usable = true;
513 dataset->head.ttl = result->head.ttl;
514 dataset->head.timeout = timeout;
516 dataset->resp.version = NSCD_VERSION;
517 dataset->resp.found = result->resp.found;
518 /* Until we find a matching entry the result is 0. */
519 dataset->resp.result = 0;
521 char *key_copy = memcpy ((char *) (dataset + 1), group, req->key_len);
523 if (dataset->resp.found)
525 const char *triplets = (const char *) (&result->resp + 1);
527 for (nscd_ssize_t i = result->resp.nresults; i > 0; --i)
529 bool success = true;
531 if (host != NULL)
532 success = strcmp (host, triplets) == 0;
533 triplets = (const char *) rawmemchr (triplets, '\0') + 1;
535 if (success && user != NULL)
536 success = strcmp (user, triplets) == 0;
537 triplets = (const char *) rawmemchr (triplets, '\0') + 1;
539 if (success && (domain == NULL || strcmp (domain, triplets) == 0))
541 dataset->resp.result = 1;
542 break;
544 triplets = (const char *) rawmemchr (triplets, '\0') + 1;
548 if (he != NULL && dh->data[0].innetgroupdata.result == dataset->resp.result)
550 /* The data has not changed. We will just bump the timeout
551 value. Note that the new record has been allocated on
552 the stack and need not be freed. */
553 dh->timeout = timeout;
554 dh->ttl = dataset->head.ttl;
555 ++dh->nreloads;
556 return timeout;
559 if (he == NULL)
561 /* We write the dataset before inserting it to the database
562 since while inserting this thread might block and so would
563 unnecessarily let the receiver wait. */
564 assert (fd != -1);
566 #ifdef HAVE_SENDFILE
567 if (__builtin_expect (db->mmap_used, 1) && cacheable)
569 assert (db->wr_fd != -1);
570 assert ((char *) &dataset->resp > (char *) db->data);
571 assert ((char *) dataset - (char *) db->head + sizeof (*dataset)
572 <= (sizeof (struct database_pers_head)
573 + db->head->module * sizeof (ref_t)
574 + db->head->data_size));
575 # ifndef __ASSUME_SENDFILE
576 ssize_t written =
577 # endif
578 sendfileall (fd, db->wr_fd,
579 (char *) &dataset->resp - (char *) db->head,
580 sizeof (innetgroup_response_header));
581 # ifndef __ASSUME_SENDFILE
582 if (written == -1 && errno == ENOSYS)
583 goto use_write;
584 # endif
586 else
588 # ifndef __ASSUME_SENDFILE
589 use_write:
590 # endif
591 #endif
592 writeall (fd, &dataset->resp, sizeof (innetgroup_response_header));
596 if (cacheable)
598 /* If necessary, we also propagate the data to disk. */
599 if (db->persistent)
601 // XXX async OK?
602 uintptr_t pval = (uintptr_t) dataset & ~pagesize_m1;
603 msync ((void *) pval,
604 ((uintptr_t) dataset & pagesize_m1) + sizeof (*dataset)
605 + req->key_len,
606 MS_ASYNC);
609 (void) cache_add (req->type, key_copy, req->key_len, &dataset->head,
610 true, db, uid, he == NULL);
612 pthread_rwlock_unlock (&db->lock);
614 /* Mark the old entry as obsolete. */
615 if (dh != NULL)
616 dh->usable = false;
619 return timeout;
623 void
624 addgetnetgrent (struct database_dyn *db, int fd, request_header *req,
625 void *key, uid_t uid)
627 struct dataset *ignore;
629 addgetnetgrentX (db, fd, req, key, uid, NULL, NULL, &ignore);
633 time_t
634 readdgetnetgrent (struct database_dyn *db, struct hashentry *he,
635 struct datahead *dh)
637 request_header req =
639 .type = GETNETGRENT,
640 .key_len = he->len
642 struct dataset *ignore;
644 return addgetnetgrentX (db, -1, &req, db->data + he->key, he->owner, he, dh,
645 &ignore);
649 void
650 addinnetgr (struct database_dyn *db, int fd, request_header *req,
651 void *key, uid_t uid)
653 addinnetgrX (db, fd, req, key, uid, NULL, NULL);
657 time_t
658 readdinnetgr (struct database_dyn *db, struct hashentry *he,
659 struct datahead *dh)
661 request_header req =
663 .type = INNETGR,
664 .key_len = he->len
667 return addinnetgrX (db, -1, &req, db->data + he->key, he->owner, he, dh);