LoongArch: Update ulps
[glibc.git] / nscd / netgroupcache.c
blob5fdcf4204ef73dd27dd74e6a1e1aefdec15621c0
1 /* Cache handling for netgroup lookup.
2 Copyright (C) 2011-2024 Free Software Foundation, Inc.
3 This file is part of the GNU C Library.
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published
7 by the Free Software Foundation; version 2 of the License, or
8 (at your option) any later version.
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, see <https://www.gnu.org/licenses/>. */
18 #include <alloca.h>
19 #include <assert.h>
20 #include <errno.h>
21 #include <libintl.h>
22 #include <stdbool.h>
23 #include <stdlib.h>
24 #include <unistd.h>
25 #include <sys/mman.h>
26 #include <scratch_buffer.h>
28 #include "../nss/netgroup.h"
29 #include "nscd.h"
30 #include "dbg_log.h"
32 #include <kernel-features.h>
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];
69 /* Send a notfound response to FD. Always returns -1 to indicate an
70 ephemeral error. */
71 static time_t
72 send_notfound (int fd)
74 if (fd != -1)
75 TEMP_FAILURE_RETRY (send (fd, &notfound, sizeof (notfound), MSG_NOSIGNAL));
76 return -1;
79 /* Sends a notfound message and prepares a notfound dataset to write to the
80 cache. Returns true if there was enough memory to allocate the dataset and
81 returns the dataset in DATASETP, total bytes to write in TOTALP and the
82 timeout in TIMEOUTP. KEY_COPY is set to point to the copy of the key in the
83 dataset. */
84 static bool
85 do_notfound (struct database_dyn *db, int fd, request_header *req,
86 const char *key, struct dataset **datasetp, ssize_t *totalp,
87 time_t *timeoutp, char **key_copy)
89 struct dataset *dataset;
90 ssize_t total;
91 time_t timeout;
92 bool cacheable = false;
94 total = sizeof (notfound);
95 timeout = time (NULL) + db->negtimeout;
97 send_notfound (fd);
99 dataset = mempool_alloc (db, sizeof (struct dataset) + req->key_len, 1);
100 /* If we cannot permanently store the result, so be it. */
101 if (dataset != NULL)
103 timeout = datahead_init_neg (&dataset->head,
104 sizeof (struct dataset) + req->key_len,
105 total, db->negtimeout);
107 /* This is the reply. */
108 memcpy (&dataset->resp, &notfound, total);
110 /* Copy the key data. */
111 memcpy (dataset->strdata, key, req->key_len);
112 *key_copy = dataset->strdata;
114 cacheable = true;
116 *timeoutp = timeout;
117 *totalp = total;
118 *datasetp = dataset;
119 return cacheable;
122 struct addgetnetgrentX_scratch
124 /* This is the result that the caller should use. It can be NULL,
125 point into buffer, or it can be in the cache. */
126 struct dataset *dataset;
128 struct scratch_buffer buffer;
130 /* Used internally in addgetnetgrentX as a staging area. */
131 struct scratch_buffer tmp;
133 /* Number of bytes in buffer that are actually used. */
134 size_t buffer_used;
137 static void
138 addgetnetgrentX_scratch_init (struct addgetnetgrentX_scratch *scratch)
140 scratch->dataset = NULL;
141 scratch_buffer_init (&scratch->buffer);
142 scratch_buffer_init (&scratch->tmp);
144 /* Reserve space for the header. */
145 scratch->buffer_used = sizeof (struct dataset);
146 static_assert (sizeof (struct dataset) < sizeof (scratch->tmp.__space),
147 "initial buffer space");
148 memset (scratch->tmp.data, 0, sizeof (struct dataset));
151 static void
152 addgetnetgrentX_scratch_free (struct addgetnetgrentX_scratch *scratch)
154 scratch_buffer_free (&scratch->buffer);
155 scratch_buffer_free (&scratch->tmp);
158 /* Copy LENGTH bytes from S into SCRATCH. Returns NULL if SCRATCH
159 could not be resized, otherwise a pointer to the copy. */
160 static char *
161 addgetnetgrentX_append_n (struct addgetnetgrentX_scratch *scratch,
162 const char *s, size_t length)
164 while (true)
166 size_t remaining = scratch->buffer.length - scratch->buffer_used;
167 if (remaining >= length)
168 break;
169 if (!scratch_buffer_grow_preserve (&scratch->buffer))
170 return NULL;
172 char *copy = scratch->buffer.data + scratch->buffer_used;
173 memcpy (copy, s, length);
174 scratch->buffer_used += length;
175 return copy;
178 /* Copy S into SCRATCH, including its null terminator. Returns false
179 if SCRATCH could not be resized. */
180 static bool
181 addgetnetgrentX_append (struct addgetnetgrentX_scratch *scratch, const char *s)
183 if (s == NULL)
184 s = "";
185 return addgetnetgrentX_append_n (scratch, s, strlen (s) + 1) != NULL;
188 /* Caller must initialize and free *SCRATCH. If the return value is
189 negative, this function has sent a notfound response. */
190 static time_t
191 addgetnetgrentX (struct database_dyn *db, int fd, request_header *req,
192 const char *key, uid_t uid, struct hashentry *he,
193 struct datahead *dh, struct addgetnetgrentX_scratch *scratch)
195 if (__glibc_unlikely (debug_level > 0))
197 if (he == NULL)
198 dbg_log (_("Haven't found \"%s\" in netgroup cache!"), key);
199 else
200 dbg_log (_("Reloading \"%s\" in netgroup cache!"), key);
203 static nss_action_list netgroup_database;
204 time_t timeout;
205 struct dataset *dataset;
206 bool cacheable = false;
207 ssize_t total;
208 bool found = false;
210 char *key_copy = NULL;
211 struct __netgrent data;
212 size_t nentries = 0;
213 size_t group_len = strlen (key) + 1;
214 struct name_list *first_needed
215 = alloca (sizeof (struct name_list) + group_len);
217 if (netgroup_database == NULL
218 && !__nss_database_get (nss_database_netgroup, &netgroup_database))
220 /* No such service. */
221 cacheable = do_notfound (db, fd, req, key, &dataset, &total, &timeout,
222 &key_copy);
223 goto maybe_cache_add;
226 memset (&data, '\0', sizeof (data));
227 first_needed->next = first_needed;
228 memcpy (first_needed->name, key, group_len);
229 data.needed_groups = first_needed;
231 while (data.needed_groups != NULL)
233 /* Add the next group to the list of those which are known. */
234 struct name_list *this_group = data.needed_groups->next;
235 if (this_group == data.needed_groups)
236 data.needed_groups = NULL;
237 else
238 data.needed_groups->next = this_group->next;
239 this_group->next = data.known_groups;
240 data.known_groups = this_group;
242 union
244 enum nss_status (*f) (const char *, struct __netgrent *);
245 void *ptr;
246 } setfct;
248 nss_action_list nip = netgroup_database;
249 int no_more = __nss_lookup (&nip, "setnetgrent", NULL, &setfct.ptr);
250 while (!no_more)
252 enum nss_status status
253 = DL_CALL_FCT (*setfct.f, (data.known_groups->name, &data));
255 if (status == NSS_STATUS_SUCCESS)
257 found = true;
258 union
260 enum nss_status (*f) (struct __netgrent *, char *, size_t,
261 int *);
262 void *ptr;
263 } getfct;
264 getfct.ptr = __nss_lookup_function (nip, "getnetgrent_r");
265 if (getfct.f != NULL)
266 while (1)
268 int e;
269 status = getfct.f (&data, scratch->tmp.data,
270 scratch->tmp.length, &e);
271 if (status == NSS_STATUS_SUCCESS)
273 if (data.type == triple_val)
275 const char *nhost = data.val.triple.host;
276 const char *nuser = data.val.triple.user;
277 const char *ndomain = data.val.triple.domain;
278 if (!(addgetnetgrentX_append (scratch, nhost)
279 && addgetnetgrentX_append (scratch, nuser)
280 && addgetnetgrentX_append (scratch, ndomain)))
281 return send_notfound (fd);
282 ++nentries;
284 else
286 /* Check that the group has not been
287 requested before. */
288 struct name_list *runp = data.needed_groups;
289 if (runp != NULL)
290 while (1)
292 if (strcmp (runp->name, data.val.group) == 0)
293 break;
295 runp = runp->next;
296 if (runp == data.needed_groups)
298 runp = NULL;
299 break;
303 if (runp == NULL)
305 runp = data.known_groups;
306 while (runp != NULL)
307 if (strcmp (runp->name, data.val.group) == 0)
308 break;
309 else
310 runp = runp->next;
313 if (runp == NULL)
315 /* A new group is requested. */
316 size_t namelen = strlen (data.val.group) + 1;
317 struct name_list *newg = alloca (sizeof (*newg)
318 + namelen);
319 memcpy (newg->name, data.val.group, namelen);
320 if (data.needed_groups == NULL)
321 data.needed_groups = newg->next = newg;
322 else
324 newg->next = data.needed_groups->next;
325 data.needed_groups->next = newg;
326 data.needed_groups = newg;
331 else if (status == NSS_STATUS_TRYAGAIN && e == ERANGE)
333 if (!scratch_buffer_grow (&scratch->tmp))
334 return send_notfound (fd);
336 else if (status == NSS_STATUS_RETURN
337 || status == NSS_STATUS_NOTFOUND
338 || status == NSS_STATUS_UNAVAIL)
339 /* This was either the last one for this group or the
340 group was empty or the NSS module had an internal
341 failure. Look at next group if available. */
342 break;
345 enum nss_status (*endfct) (struct __netgrent *);
346 endfct = __nss_lookup_function (nip, "endnetgrent");
347 if (endfct != NULL)
348 (void) DL_CALL_FCT (*endfct, (&data));
350 break;
353 no_more = __nss_next2 (&nip, "setnetgrent", NULL, &setfct.ptr,
354 status, 0);
358 /* No results. Return a failure and write out a notfound record in the
359 cache. */
360 if (!found)
362 cacheable = do_notfound (db, fd, req, key, &dataset, &total, &timeout,
363 &key_copy);
364 goto maybe_cache_add;
367 /* Capture the result size without the key appended. */
368 total = scratch->buffer_used;
370 /* Make a copy of the key. The scratch buffer must not move after
371 this point. */
372 key_copy = addgetnetgrentX_append_n (scratch, key, req->key_len);
373 if (key_copy == NULL)
374 return send_notfound (fd);
376 /* Fill in the dataset. */
377 dataset = scratch->buffer.data;
378 timeout = datahead_init_pos (&dataset->head, total + req->key_len,
379 total - offsetof (struct dataset, resp),
380 he == NULL ? 0 : dh->nreloads + 1,
381 db->postimeout);
383 dataset->resp.version = NSCD_VERSION;
384 dataset->resp.found = 1;
385 dataset->resp.nresults = nentries;
386 dataset->resp.result_len = total - sizeof (*dataset);
388 /* Now we can determine whether on refill we have to create a new
389 record or not. */
390 if (he != NULL)
392 assert (fd == -1);
394 if (dataset->head.allocsize == dh->allocsize
395 && dataset->head.recsize == dh->recsize
396 && memcmp (&dataset->resp, dh->data,
397 dh->allocsize - offsetof (struct dataset, resp)) == 0)
399 /* The data has not changed. We will just bump the timeout
400 value. Note that the new record has been allocated on
401 the stack and need not be freed. */
402 dh->timeout = dataset->head.timeout;
403 dh->ttl = dataset->head.ttl;
404 ++dh->nreloads;
405 dataset = (struct dataset *) dh;
407 goto out;
412 struct dataset *newp
413 = (struct dataset *) mempool_alloc (db, total + req->key_len, 1);
414 if (__glibc_likely (newp != NULL))
416 /* Adjust pointer into the memory block. */
417 key_copy = (char *) newp + (key_copy - (char *) dataset);
419 dataset = memcpy (newp, dataset, total + req->key_len);
420 cacheable = true;
422 if (he != NULL)
423 /* Mark the old record as obsolete. */
424 dh->usable = false;
428 if (he == NULL && fd != -1)
429 /* We write the dataset before inserting it to the database since
430 while inserting this thread might block and so would
431 unnecessarily let the receiver wait. */
432 writeall (fd, &dataset->resp, dataset->head.recsize);
434 maybe_cache_add:
435 if (cacheable)
437 /* If necessary, we also propagate the data to disk. */
438 if (db->persistent)
440 // XXX async OK?
441 uintptr_t pval = (uintptr_t) dataset & ~pagesize_m1;
442 msync ((void *) pval,
443 ((uintptr_t) dataset & pagesize_m1) + total + req->key_len,
444 MS_ASYNC);
447 (void) cache_add (req->type, key_copy, req->key_len, &dataset->head,
448 true, db, uid, he == NULL);
450 pthread_rwlock_unlock (&db->lock);
452 /* Mark the old entry as obsolete. */
453 if (dh != NULL)
454 dh->usable = false;
457 out:
458 scratch->dataset = dataset;
460 return timeout;
464 static time_t
465 addinnetgrX (struct database_dyn *db, int fd, request_header *req,
466 char *key, uid_t uid, struct hashentry *he,
467 struct datahead *dh)
469 const char *group = key;
470 key = strchr (key, '\0') + 1;
471 size_t group_len = key - group;
472 const char *host = *key++ ? key : NULL;
473 if (host != NULL)
474 key = strchr (key, '\0') + 1;
475 const char *user = *key++ ? key : NULL;
476 if (user != NULL)
477 key = strchr (key, '\0') + 1;
478 const char *domain = *key++ ? key : NULL;
479 struct addgetnetgrentX_scratch scratch;
481 addgetnetgrentX_scratch_init (&scratch);
483 if (__glibc_unlikely (debug_level > 0))
485 if (he == NULL)
486 dbg_log (_("Haven't found \"%s (%s,%s,%s)\" in netgroup cache!"),
487 group, host ?: "", user ?: "", domain ?: "");
488 else
489 dbg_log (_("Reloading \"%s (%s,%s,%s)\" in netgroup cache!"),
490 group, host ?: "", user ?: "", domain ?: "");
493 struct dataset *result = (struct dataset *) cache_search (GETNETGRENT,
494 group, group_len,
495 db, uid);
496 time_t timeout;
497 if (result != NULL)
498 timeout = result->head.timeout;
499 else
501 request_header req_get =
503 .type = GETNETGRENT,
504 .key_len = group_len
506 timeout = addgetnetgrentX (db, -1, &req_get, group, uid, NULL, NULL,
507 &scratch);
508 result = scratch.dataset;
509 if (timeout < 0)
510 goto out;
513 struct indataset
515 struct datahead head;
516 innetgroup_response_header resp;
517 } *dataset
518 = (struct indataset *) mempool_alloc (db,
519 sizeof (*dataset) + req->key_len,
521 bool cacheable = true;
522 if (__glibc_unlikely (dataset == NULL))
524 cacheable = false;
525 /* The alloca is safe because nscd_run_worker verifies that
526 key_len is not larger than MAXKEYLEN. */
527 dataset = alloca (sizeof (*dataset) + req->key_len);
530 datahead_init_pos (&dataset->head, sizeof (*dataset) + req->key_len,
531 sizeof (innetgroup_response_header),
532 he == NULL ? 0 : dh->nreloads + 1,
533 result == NULL ? db->negtimeout : result->head.ttl);
534 /* Set the notfound status and timeout based on the result from
535 getnetgrent. */
536 dataset->head.notfound = result == NULL || result->head.notfound;
537 dataset->head.timeout = timeout;
539 dataset->resp.version = NSCD_VERSION;
540 dataset->resp.found = result != NULL && result->resp.found;
541 /* Until we find a matching entry the result is 0. */
542 dataset->resp.result = 0;
544 char *key_copy = memcpy ((char *) (dataset + 1), group, req->key_len);
546 if (dataset->resp.found)
548 const char *triplets = (const char *) (&result->resp + 1);
550 for (nscd_ssize_t i = result->resp.nresults; i > 0; --i)
552 bool success = true;
554 /* For the host, user and domain in each triplet, we assume success
555 if the value is blank because that is how the wildcard entry to
556 match anything is stored in the netgroup cache. */
557 if (host != NULL && *triplets != '\0')
558 success = strcmp (host, triplets) == 0;
559 triplets = strchr (triplets, '\0') + 1;
561 if (success && user != NULL && *triplets != '\0')
562 success = strcmp (user, triplets) == 0;
563 triplets = strchr (triplets, '\0') + 1;
565 if (success && (domain == NULL || *triplets == '\0'
566 || strcmp (domain, triplets) == 0))
568 dataset->resp.result = 1;
569 break;
571 triplets = strchr (triplets, '\0') + 1;
575 if (he != NULL && dh->data[0].innetgroupdata.result == dataset->resp.result)
577 /* The data has not changed. We will just bump the timeout
578 value. Note that the new record has been allocated on
579 the stack and need not be freed. */
580 dh->timeout = timeout;
581 dh->ttl = dataset->head.ttl;
582 ++dh->nreloads;
583 if (cacheable)
584 pthread_rwlock_unlock (&db->lock);
585 goto out;
588 /* addgetnetgrentX may have already sent a notfound response. Do
589 not send another one. */
590 if (he == NULL && dataset->resp.found)
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 writeall (fd, &dataset->resp, sizeof (innetgroup_response_header));
600 if (cacheable)
602 /* If necessary, we also propagate the data to disk. */
603 if (db->persistent)
605 // XXX async OK?
606 uintptr_t pval = (uintptr_t) dataset & ~pagesize_m1;
607 msync ((void *) pval,
608 ((uintptr_t) dataset & pagesize_m1) + sizeof (*dataset)
609 + req->key_len,
610 MS_ASYNC);
613 (void) cache_add (req->type, key_copy, req->key_len, &dataset->head,
614 true, db, uid, he == NULL);
616 pthread_rwlock_unlock (&db->lock);
618 /* Mark the old entry as obsolete. */
619 if (dh != NULL)
620 dh->usable = false;
623 out:
624 addgetnetgrentX_scratch_free (&scratch);
625 return timeout;
629 static time_t
630 addgetnetgrentX_ignore (struct database_dyn *db, int fd, request_header *req,
631 const char *key, uid_t uid, struct hashentry *he,
632 struct datahead *dh)
634 struct addgetnetgrentX_scratch scratch;
635 addgetnetgrentX_scratch_init (&scratch);
636 time_t timeout = addgetnetgrentX (db, fd, req, key, uid, he, dh, &scratch);
637 addgetnetgrentX_scratch_free (&scratch);
638 if (timeout < 0)
639 timeout = 0;
640 return timeout;
643 void
644 addgetnetgrent (struct database_dyn *db, int fd, request_header *req,
645 void *key, uid_t uid)
647 addgetnetgrentX_ignore (db, fd, req, key, uid, NULL, NULL);
651 time_t
652 readdgetnetgrent (struct database_dyn *db, struct hashentry *he,
653 struct datahead *dh)
655 request_header req =
657 .type = GETNETGRENT,
658 .key_len = he->len
660 return addgetnetgrentX_ignore
661 (db, -1, &req, db->data + he->key, he->owner, he, dh);
665 void
666 addinnetgr (struct database_dyn *db, int fd, request_header *req,
667 void *key, uid_t uid)
669 addinnetgrX (db, fd, req, key, uid, NULL, NULL);
673 time_t
674 readdinnetgr (struct database_dyn *db, struct hashentry *he,
675 struct datahead *dh)
677 request_header req =
679 .type = INNETGR,
680 .key_len = he->len
683 time_t timeout = addinnetgrX (db, -1, &req, db->data + he->key, he->owner,
684 he, dh);
685 if (timeout < 0)
686 timeout = 0;
687 return timeout;