Replace lll_private_futex_* (*) with lll_futex_* (*, LLL_PRIVATE).
[kopensolaris-gnu/glibc.git] / nscd / initgrcache.c
1 /* Cache handling for host lookup.
2    Copyright (C) 2004, 2005, 2006 Free Software Foundation, Inc.
3    This file is part of the GNU C Library.
4    Contributed by Ulrich Drepper <drepper@redhat.com>, 2004.
5
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.
10
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.
15
16    You should have received a copy of the GNU General Public License
17    along with this program; if not, write to the Free Software Foundation,
18    Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.  */
19
20 #include <assert.h>
21 #include <errno.h>
22 #include <grp.h>
23 #include <libintl.h>
24 #include <string.h>
25 #include <time.h>
26 #include <unistd.h>
27 #include <sys/mman.h>
28
29 #include "dbg_log.h"
30 #include "nscd.h"
31 #ifdef HAVE_SENDFILE
32 # include <kernel-features.h>
33 #endif
34
35 #include "../nss/nsswitch.h"
36
37
38 /* Type of the lookup function.  */
39 typedef enum nss_status (*initgroups_dyn_function) (const char *, gid_t,
40                                                     long int *, long int *,
41                                                     gid_t **, long int, int *);
42
43
44 static const initgr_response_header notfound =
45 {
46   .version = NSCD_VERSION,
47   .found = 0,
48   .ngrps = 0
49 };
50
51
52 #include "../grp/compat-initgroups.c"
53
54
55 static void
56 addinitgroupsX (struct database_dyn *db, int fd, request_header *req,
57                 void *key, uid_t uid, struct hashentry *he,
58                 struct datahead *dh)
59 {
60   /* Search for the entry matching the key.  Please note that we don't
61      look again in the table whether the dataset is now available.  We
62      simply insert it.  It does not matter if it is in there twice.  The
63      pruning function only will look at the timestamp.  */
64
65
66   /* We allocate all data in one memory block: the iov vector,
67      the response header and the dataset itself.  */
68   struct dataset
69   {
70     struct datahead head;
71     initgr_response_header resp;
72     char strdata[0];
73   } *dataset = NULL;
74
75   if (__builtin_expect (debug_level > 0, 0))
76     {
77       if (he == NULL)
78         dbg_log (_("Haven't found \"%s\" in group cache!"), (char *) key);
79       else
80         dbg_log (_("Reloading \"%s\" in group cache!"), (char *) key);
81     }
82
83   static service_user *group_database;
84   service_user *nip = NULL;
85   int no_more;
86
87   if (group_database != NULL)
88     {
89       nip = group_database;
90       no_more = 0;
91     }
92   else
93     no_more = __nss_database_lookup ("group", NULL,
94                                      "compat [NOTFOUND=return] files", &nip);
95
96  /* We always use sysconf even if NGROUPS_MAX is defined.  That way, the
97      limit can be raised in the kernel configuration without having to
98      recompile libc.  */
99   long int limit = __sysconf (_SC_NGROUPS_MAX);
100
101   long int size;
102   if (limit > 0)
103     /* We limit the size of the intially allocated array.  */
104     size = MIN (limit, 64);
105   else
106     /* No fixed limit on groups.  Pick a starting buffer size.  */
107     size = 16;
108
109   long int start = 0;
110   bool all_tryagain = true;
111   bool any_success = false;
112
113   /* This is temporary memory, we need not (ad must not) call
114      mempool_alloc.  */
115   // XXX This really should use alloca.  need to change the backends.
116   gid_t *groups = (gid_t *) malloc (size * sizeof (gid_t));
117   if (__builtin_expect (groups == NULL, 0))
118     /* No more memory.  */
119     goto out;
120
121   /* Nothing added yet.  */
122   while (! no_more)
123     {
124       long int prev_start = start;
125       enum nss_status status;
126       initgroups_dyn_function fct;
127       fct = __nss_lookup_function (nip, "initgroups_dyn");
128
129       if (fct == NULL)
130         {
131           status = compat_call (nip, key, -1, &start, &size, &groups,
132                                 limit, &errno);
133
134           if (nss_next_action (nip, NSS_STATUS_UNAVAIL) != NSS_ACTION_CONTINUE)
135             break;
136         }
137       else
138         status = DL_CALL_FCT (fct, (key, -1, &start, &size, &groups,
139                                     limit, &errno));
140
141       /* Remove duplicates.  */
142       long int cnt = prev_start;
143       while (cnt < start)
144         {
145           long int inner;
146           for (inner = 0; inner < prev_start; ++inner)
147             if (groups[inner] == groups[cnt])
148               break;
149
150           if (inner < prev_start)
151             groups[cnt] = groups[--start];
152           else
153             ++cnt;
154         }
155
156       if (status != NSS_STATUS_TRYAGAIN)
157         all_tryagain = false;
158
159       /* This is really only for debugging.  */
160       if (NSS_STATUS_TRYAGAIN > status || status > NSS_STATUS_RETURN)
161         __libc_fatal ("illegal status in internal_getgrouplist");
162
163       any_success |= status == NSS_STATUS_SUCCESS;
164
165       if (status != NSS_STATUS_SUCCESS
166           && nss_next_action (nip, status) == NSS_ACTION_RETURN)
167          break;
168
169       if (nip->next == NULL)
170         no_more = -1;
171       else
172         nip = nip->next;
173     }
174
175   ssize_t total;
176   ssize_t written;
177  out:
178   if (!any_success)
179     {
180       /* Nothing found.  Create a negative result record.  */
181       written = total = sizeof (notfound);
182
183       if (he != NULL && all_tryagain)
184         {
185           /* If we have an old record available but cannot find one now
186              because the service is not available we keep the old record
187              and make sure it does not get removed.  */
188           if (reload_count != UINT_MAX && dh->nreloads == reload_count)
189             /* Do not reset the value if we never not reload the record.  */
190             dh->nreloads = reload_count - 1;
191         }
192       else
193         {
194           /* We have no data.  This means we send the standard reply for this
195              case.  */
196           if (fd != -1)
197             written = TEMP_FAILURE_RETRY (send (fd, &notfound, total,
198                                                 MSG_NOSIGNAL));
199
200           dataset = mempool_alloc (db, sizeof (struct dataset) + req->key_len);
201           /* If we cannot permanently store the result, so be it.  */
202           if (dataset != NULL)
203             {
204               dataset->head.allocsize = sizeof (struct dataset) + req->key_len;
205               dataset->head.recsize = total;
206               dataset->head.notfound = true;
207               dataset->head.nreloads = 0;
208               dataset->head.usable = true;
209
210               /* Compute the timeout time.  */
211               dataset->head.timeout = time (NULL) + db->negtimeout;
212
213               /* This is the reply.  */
214               memcpy (&dataset->resp, &notfound, total);
215
216               /* Copy the key data.  */
217               char *key_copy = memcpy (dataset->strdata, key, req->key_len);
218
219               /* If necessary, we also propagate the data to disk.  */
220               if (db->persistent)
221                 {
222                   // XXX async OK?
223                   uintptr_t pval = (uintptr_t) dataset & ~pagesize_m1;
224                   msync ((void *) pval,
225                          ((uintptr_t) dataset & pagesize_m1)
226                          + sizeof (struct dataset) + req->key_len, MS_ASYNC);
227                 }
228
229               /* Now get the lock to safely insert the records.  */
230               pthread_rwlock_rdlock (&db->lock);
231
232               if (cache_add (req->type, key_copy, req->key_len,
233                              &dataset->head, true, db, uid) < 0)
234                 /* Ensure the data can be recovered.  */
235                 dataset->head.usable = false;
236
237               pthread_rwlock_unlock (&db->lock);
238
239               /* Mark the old entry as obsolete.  */
240               if (dh != NULL)
241                 dh->usable = false;
242             }
243           else
244             ++db->head->addfailed;
245         }
246     }
247   else
248     {
249
250       written = total = sizeof (struct dataset) + start * sizeof (int32_t);
251
252       /* If we refill the cache, first assume the reconrd did not
253          change.  Allocate memory on the cache since it is likely
254          discarded anyway.  If it turns out to be necessary to have a
255          new record we can still allocate real memory.  */
256       bool alloca_used = false;
257       dataset = NULL;
258
259       if (he == NULL)
260         {
261           dataset = (struct dataset *) mempool_alloc (db,
262                                                       total + req->key_len);
263           if (dataset == NULL)
264             ++db->head->addfailed;
265         }
266
267       if (dataset == NULL)
268         {
269           /* We cannot permanently add the result in the moment.  But
270              we can provide the result as is.  Store the data in some
271              temporary memory.  */
272           dataset = (struct dataset *) alloca (total + req->key_len);
273
274           /* We cannot add this record to the permanent database.  */
275           alloca_used = true;
276         }
277
278       dataset->head.allocsize = total + req->key_len;
279       dataset->head.recsize = total - offsetof (struct dataset, resp);
280       dataset->head.notfound = false;
281       dataset->head.nreloads = he == NULL ? 0 : (dh->nreloads + 1);
282       dataset->head.usable = true;
283
284       /* Compute the timeout time.  */
285       dataset->head.timeout = time (NULL) + db->postimeout;
286
287       dataset->resp.version = NSCD_VERSION;
288       dataset->resp.found = 1;
289       dataset->resp.ngrps = start;
290
291       char *cp = dataset->strdata;
292
293       /* Copy the GID values.  If the size of the types match this is
294          very simple.  */
295       if (sizeof (gid_t) == sizeof (int32_t))
296         cp = mempcpy (cp, groups, start * sizeof (gid_t));
297       else
298         {
299           gid_t *gcp = (gid_t *) cp;
300
301           for (int i = 0; i < start; ++i)
302             *gcp++ = groups[i];
303
304           cp = (char *) gcp;
305         }
306
307       /* Finally the user name.  */
308       memcpy (cp, key, req->key_len);
309
310       /* Now we can determine whether on refill we have to create a new
311          record or not.  */
312       if (he != NULL)
313         {
314           assert (fd == -1);
315
316           if (total + req->key_len == dh->allocsize
317               && total - offsetof (struct dataset, resp) == dh->recsize
318               && memcmp (&dataset->resp, dh->data,
319                          dh->allocsize - offsetof (struct dataset, resp)) == 0)
320             {
321               /* The data has not changed.  We will just bump the
322                  timeout value.  Note that the new record has been
323                  allocated on the stack and need not be freed.  */
324               dh->timeout = dataset->head.timeout;
325               ++dh->nreloads;
326             }
327           else
328             {
329               /* We have to create a new record.  Just allocate
330                  appropriate memory and copy it.  */
331               struct dataset *newp
332                 = (struct dataset *) mempool_alloc (db, total + req->key_len);
333               if (newp != NULL)
334                 {
335                   /* Adjust pointer into the memory block.  */
336                   cp = (char *) newp + (cp - (char *) dataset);
337
338                   dataset = memcpy (newp, dataset, total + req->key_len);
339                   alloca_used = false;
340                 }
341
342               /* Mark the old record as obsolete.  */
343               dh->usable = false;
344             }
345         }
346       else
347         {
348           /* We write the dataset before inserting it to the database
349              since while inserting this thread might block and so would
350              unnecessarily let the receiver wait.  */
351           assert (fd != -1);
352
353 #ifdef HAVE_SENDFILE
354           if (__builtin_expect (db->mmap_used, 1) && !alloca_used)
355             {
356               assert (db->wr_fd != -1);
357               assert ((char *) &dataset->resp > (char *) db->data);
358               assert ((char *) &dataset->resp - (char *) db->head
359                       + total
360                       <= (sizeof (struct database_pers_head)
361                           + db->head->module * sizeof (ref_t)
362                           + db->head->data_size));
363               written = sendfileall (fd, db->wr_fd,
364                                      (char *) &dataset->resp
365                                      - (char *) db->head, total);
366 # ifndef __ASSUME_SENDFILE
367               if (written == -1 && errno == ENOSYS)
368                 goto use_write;
369 # endif
370             }
371           else
372 # ifndef __ASSUME_SENDFILE
373           use_write:
374 # endif
375 #endif
376             written = writeall (fd, &dataset->resp, total);
377         }
378
379
380       /* Add the record to the database.  But only if it has not been
381          stored on the stack.  */
382       if (! alloca_used)
383         {
384           /* If necessary, we also propagate the data to disk.  */
385           if (db->persistent)
386             {
387               // XXX async OK?
388               uintptr_t pval = (uintptr_t) dataset & ~pagesize_m1;
389               msync ((void *) pval,
390                      ((uintptr_t) dataset & pagesize_m1) + total +
391                      req->key_len, MS_ASYNC);
392             }
393
394           /* Now get the lock to safely insert the records.  */
395           pthread_rwlock_rdlock (&db->lock);
396
397           if (cache_add (INITGROUPS, cp, req->key_len, &dataset->head, true,
398                          db, uid) < 0)
399             /* Could not allocate memory.  Make sure the data gets
400                discarded.  */
401             dataset->head.usable = false;
402
403           pthread_rwlock_unlock (&db->lock);
404         }
405     }
406
407   free (groups);
408
409   if (__builtin_expect (written != total, 0) && debug_level > 0)
410     {
411       char buf[256];
412       dbg_log (_("short write in %s: %s"), __FUNCTION__,
413                strerror_r (errno, buf, sizeof (buf)));
414     }
415 }
416
417
418 void
419 addinitgroups (struct database_dyn *db, int fd, request_header *req, void *key,
420                uid_t uid)
421 {
422   addinitgroupsX (db, fd, req, key, uid, NULL, NULL);
423 }
424
425
426 void
427 readdinitgroups (struct database_dyn *db, struct hashentry *he,
428                  struct datahead *dh)
429 {
430   request_header req =
431     {
432       .type = INITGROUPS,
433       .key_len = he->len
434     };
435
436   addinitgroupsX (db, -1, &req, db->data + he->key, he->owner, he, dh);
437 }