(addinitgroupsX): Use send with MSG_NOSIGNAL not write to send reply.
[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 (send (fd, &notfound, total,
192                                                 MSG_NOSIGNAL));
193
194           dataset = mempool_alloc (db, sizeof (struct dataset) + req->key_len);
195           /* If we cannot permanently store the result, so be it.  */
196           if (dataset != NULL)
197             {
198               dataset->head.allocsize = sizeof (struct dataset) + req->key_len;
199               dataset->head.recsize = total;
200               dataset->head.notfound = true;
201               dataset->head.nreloads = 0;
202               dataset->head.usable = true;
203
204               /* Compute the timeout time.  */
205               dataset->head.timeout = time (NULL) + db->negtimeout;
206
207               /* This is the reply.  */
208               memcpy (&dataset->resp, &notfound, total);
209
210               /* Copy the key data.  */
211               char *key_copy = memcpy (dataset->strdata, key, req->key_len);
212
213               /* If necessary, we also propagate the data to disk.  */
214               if (db->persistent)
215                 {
216                   // XXX async OK?
217                   uintptr_t pval = (uintptr_t) dataset & ~pagesize_m1;
218                   msync ((void *) pval,
219                          ((uintptr_t) dataset & pagesize_m1)
220                          + sizeof (struct dataset) + req->key_len, MS_ASYNC);
221                 }
222
223               /* Now get the lock to safely insert the records.  */
224               pthread_rwlock_rdlock (&db->lock);
225
226               if (cache_add (req->type, key_copy, req->key_len,
227                              &dataset->head, true, db, uid) < 0)
228                 /* Ensure the data can be recovered.  */
229                 dataset->head.usable = false;
230
231               pthread_rwlock_unlock (&db->lock);
232
233               /* Mark the old entry as obsolete.  */
234               if (dh != NULL)
235                 dh->usable = false;
236             }
237           else
238             ++db->head->addfailed;
239         }
240     }
241   else
242     {
243
244       written = total = sizeof (struct dataset) + start * sizeof (int32_t);
245
246       /* If we refill the cache, first assume the reconrd did not
247          change.  Allocate memory on the cache since it is likely
248          discarded anyway.  If it turns out to be necessary to have a
249          new record we can still allocate real memory.  */
250       bool alloca_used = false;
251       dataset = NULL;
252
253       if (he == NULL)
254         {
255           dataset = (struct dataset *) mempool_alloc (db,
256                                                       total + req->key_len);
257           if (dataset == NULL)
258             ++db->head->addfailed;
259         }
260
261       if (dataset == NULL)
262         {
263           /* We cannot permanently add the result in the moment.  But
264              we can provide the result as is.  Store the data in some
265              temporary memory.  */
266           dataset = (struct dataset *) alloca (total + req->key_len);
267
268           /* We cannot add this record to the permanent database.  */
269           alloca_used = true;
270         }
271
272       dataset->head.allocsize = total + req->key_len;
273       dataset->head.recsize = total - offsetof (struct dataset, resp);
274       dataset->head.notfound = false;
275       dataset->head.nreloads = he == NULL ? 0 : (dh->nreloads + 1);
276       dataset->head.usable = true;
277
278       /* Compute the timeout time.  */
279       dataset->head.timeout = time (NULL) + db->postimeout;
280
281       dataset->resp.version = NSCD_VERSION;
282       dataset->resp.found = 1;
283       dataset->resp.ngrps = start;
284
285       char *cp = dataset->strdata;
286
287       /* Copy the GID values.  If the size of the types match this is
288          very simple.  */
289       if (sizeof (gid_t) == sizeof (int32_t))
290         cp = mempcpy (cp, groups, start * sizeof (gid_t));
291       else
292         {
293           gid_t *gcp = (gid_t *) cp;
294
295           for (int i = 0; i < start; ++i)
296             *gcp++ = groups[i];
297
298           cp = (char *) gcp;
299         }
300
301       /* Finally the user name.  */
302       memcpy (cp, key, req->key_len);
303
304       /* Now we can determine whether on refill we have to create a new
305          record or not.  */
306       if (he != NULL)
307         {
308           assert (fd == -1);
309
310           if (total + req->key_len == dh->allocsize
311               && total - offsetof (struct dataset, resp) == dh->recsize
312               && memcmp (&dataset->resp, dh->data,
313                          dh->allocsize - offsetof (struct dataset, resp)) == 0)
314             {
315               /* The data has not changed.  We will just bump the
316                  timeout value.  Note that the new record has been
317                  allocated on the stack and need not be freed.  */
318               dh->timeout = dataset->head.timeout;
319               ++dh->nreloads;
320             }
321           else
322             {
323               /* We have to create a new record.  Just allocate
324                  appropriate memory and copy it.  */
325               struct dataset *newp
326                 = (struct dataset *) mempool_alloc (db, total + req->key_len);
327               if (newp != NULL)
328                 {
329                   /* Adjust pointer into the memory block.  */
330                   cp = (char *) newp + (cp - (char *) dataset);
331
332                   dataset = memcpy (newp, dataset, total + req->key_len);
333                   alloca_used = false;
334                 }
335
336               /* Mark the old record as obsolete.  */
337               dh->usable = false;
338             }
339         }
340       else
341         {
342           /* We write the dataset before inserting it to the database
343              since while inserting this thread might block and so would
344              unnecessarily let the receiver wait.  */
345           assert (fd != -1);
346
347           written = writeall (fd, &dataset->resp, total);
348         }
349
350
351       /* Add the record to the database.  But only if it has not been
352          stored on the stack.  */
353       if (! alloca_used)
354         {
355           /* If necessary, we also propagate the data to disk.  */
356           if (db->persistent)
357             {
358               // XXX async OK?
359               uintptr_t pval = (uintptr_t) dataset & ~pagesize_m1;
360               msync ((void *) pval,
361                      ((uintptr_t) dataset & pagesize_m1) + total +
362                      req->key_len, MS_ASYNC);
363             }
364
365           /* Now get the lock to safely insert the records.  */
366           pthread_rwlock_rdlock (&db->lock);
367
368           if (cache_add (INITGROUPS, cp, req->key_len, &dataset->head, true,
369                          db, uid) < 0)
370             /* Could not allocate memory.  Make sure the data gets
371                discarded.  */
372             dataset->head.usable = false;
373
374           pthread_rwlock_unlock (&db->lock);
375         }
376     }
377
378   free (groups);
379
380   if (__builtin_expect (written != total, 0) && debug_level > 0)
381     {
382       char buf[256];
383       dbg_log (_("short write in %s: %s"), __FUNCTION__,
384                strerror_r (errno, buf, sizeof (buf)));
385     }
386 }
387
388
389 void
390 addinitgroups (struct database_dyn *db, int fd, request_header *req, void *key,
391                uid_t uid)
392 {
393   addinitgroupsX (db, fd, req, key, uid, NULL, NULL);
394 }
395
396
397 void
398 readdinitgroups (struct database_dyn *db, struct hashentry *he,
399                  struct datahead *dh)
400 {
401   request_header req =
402     {
403       .type = INITGROUPS,
404       .key_len = he->len
405     };
406
407   addinitgroupsX (db, -1, &req, db->data + he->key, he->owner, he, dh);
408 }