3355df5164d86f3e4607785e53d8bbd03635ee20
[kopensolaris-gnu/glibc.git] / nscd / initgrcache.c
1 /* Cache handling for host lookup.
2    Copyright (C) 2004, 2005, 2006, 2008 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 (and 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                                    IDX_result_data);
202           /* If we cannot permanently store the result, so be it.  */
203           if (dataset != NULL)
204             {
205               dataset->head.allocsize = sizeof (struct dataset) + req->key_len;
206               dataset->head.recsize = total;
207               dataset->head.notfound = true;
208               dataset->head.nreloads = 0;
209               dataset->head.usable = true;
210
211               /* Compute the timeout time.  */
212               dataset->head.timeout = time (NULL) + db->negtimeout;
213
214               /* This is the reply.  */
215               memcpy (&dataset->resp, &notfound, total);
216
217               /* Copy the key data.  */
218               char *key_copy = memcpy (dataset->strdata, key, req->key_len);
219
220               /* If necessary, we also propagate the data to disk.  */
221               if (db->persistent)
222                 {
223                   // XXX async OK?
224                   uintptr_t pval = (uintptr_t) dataset & ~pagesize_m1;
225                   msync ((void *) pval,
226                          ((uintptr_t) dataset & pagesize_m1)
227                          + sizeof (struct dataset) + req->key_len, MS_ASYNC);
228                 }
229
230               /* Now get the lock to safely insert the records.  */
231               pthread_rwlock_rdlock (&db->lock);
232
233               (void) cache_add (req->type, key_copy, req->key_len,
234                                 &dataset->head, true, db, uid, he == NULL);
235
236               pthread_rwlock_unlock (&db->lock);
237
238               /* Mark the old entry as obsolete.  */
239               if (dh != NULL)
240                 dh->usable = false;
241             }
242           else
243             ++db->head->addfailed;
244         }
245     }
246   else
247     {
248
249       written = total = sizeof (struct dataset) + start * sizeof (int32_t);
250
251       /* If we refill the cache, first assume the reconrd did not
252          change.  Allocate memory on the cache since it is likely
253          discarded anyway.  If it turns out to be necessary to have a
254          new record we can still allocate real memory.  */
255       bool alloca_used = false;
256       dataset = NULL;
257
258       if (he == NULL)
259         {
260           dataset = (struct dataset *) mempool_alloc (db,
261                                                       total + req->key_len,
262                                                       IDX_result_data);
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                                                     IDX_result_data);
334               if (newp != NULL)
335                 {
336                   /* Adjust pointer into the memory block.  */
337                   cp = (char *) newp + (cp - (char *) dataset);
338
339                   dataset = memcpy (newp, dataset, total + req->key_len);
340                   alloca_used = false;
341                 }
342               else
343                 ++db->head->addfailed;
344
345               /* Mark the old record as obsolete.  */
346               dh->usable = false;
347             }
348         }
349       else
350         {
351           /* We write the dataset before inserting it to the database
352              since while inserting this thread might block and so would
353              unnecessarily let the receiver wait.  */
354           assert (fd != -1);
355
356 #ifdef HAVE_SENDFILE
357           if (__builtin_expect (db->mmap_used, 1) && !alloca_used)
358             {
359               assert (db->wr_fd != -1);
360               assert ((char *) &dataset->resp > (char *) db->data);
361               assert ((char *) &dataset->resp - (char *) db->head
362                       + total
363                       <= (sizeof (struct database_pers_head)
364                           + db->head->module * sizeof (ref_t)
365                           + db->head->data_size));
366               written = sendfileall (fd, db->wr_fd,
367                                      (char *) &dataset->resp
368                                      - (char *) db->head, total);
369 # ifndef __ASSUME_SENDFILE
370               if (written == -1 && errno == ENOSYS)
371                 goto use_write;
372 # endif
373             }
374           else
375 # ifndef __ASSUME_SENDFILE
376           use_write:
377 # endif
378 #endif
379             written = writeall (fd, &dataset->resp, total);
380         }
381
382
383       /* Add the record to the database.  But only if it has not been
384          stored on the stack.  */
385       if (! alloca_used)
386         {
387           /* If necessary, we also propagate the data to disk.  */
388           if (db->persistent)
389             {
390               // XXX async OK?
391               uintptr_t pval = (uintptr_t) dataset & ~pagesize_m1;
392               msync ((void *) pval,
393                      ((uintptr_t) dataset & pagesize_m1) + total +
394                      req->key_len, MS_ASYNC);
395             }
396
397           /* Now get the lock to safely insert the records.  */
398           pthread_rwlock_rdlock (&db->lock);
399
400           (void) cache_add (INITGROUPS, cp, req->key_len, &dataset->head, true,
401                             db, uid, he == NULL);
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 }