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