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