Remove Linux kernel version ambiguity in comment added by previous commit.
[platform/upstream/glibc.git] / nscd / initgrcache.c
1 /* Cache handling for host lookup.
2    Copyright (C) 2004-2013 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 (__builtin_expect (debug_level > 0, 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 = NULL;
84   int no_more;
85
86   if (group_database != NULL)
87     {
88       nip = group_database;
89       no_more = 0;
90     }
91   else
92     no_more = __nss_database_lookup ("group", NULL,
93                                      "compat [NOTFOUND=return] files", &nip);
94
95  /* We always use sysconf even if NGROUPS_MAX is defined.  That way, the
96      limit can be raised in the kernel configuration without having to
97      recompile libc.  */
98   long int limit = __sysconf (_SC_NGROUPS_MAX);
99
100   long int size;
101   if (limit > 0)
102     /* We limit the size of the intially allocated array.  */
103     size = MIN (limit, 64);
104   else
105     /* No fixed limit on groups.  Pick a starting buffer size.  */
106     size = 16;
107
108   long int start = 0;
109   bool all_tryagain = true;
110   bool any_success = false;
111
112   /* This is temporary memory, we need not (and must not) call
113      mempool_alloc.  */
114   // XXX This really should use alloca.  need to change the backends.
115   gid_t *groups = (gid_t *) malloc (size * sizeof (gid_t));
116   if (__builtin_expect (groups == NULL, 0))
117     /* No more memory.  */
118     goto out;
119
120   /* Nothing added yet.  */
121   while (! no_more)
122     {
123       long int prev_start = start;
124       enum nss_status status;
125       initgroups_dyn_function fct;
126       fct = __nss_lookup_function (nip, "initgroups_dyn");
127
128       if (fct == NULL)
129         {
130           status = compat_call (nip, key, -1, &start, &size, &groups,
131                                 limit, &errno);
132
133           if (nss_next_action (nip, NSS_STATUS_UNAVAIL) != NSS_ACTION_CONTINUE)
134             break;
135         }
136       else
137         status = DL_CALL_FCT (fct, (key, -1, &start, &size, &groups,
138                                     limit, &errno));
139
140       /* Remove duplicates.  */
141       long int cnt = prev_start;
142       while (cnt < start)
143         {
144           long int inner;
145           for (inner = 0; inner < prev_start; ++inner)
146             if (groups[inner] == groups[cnt])
147               break;
148
149           if (inner < prev_start)
150             groups[cnt] = groups[--start];
151           else
152             ++cnt;
153         }
154
155       if (status != NSS_STATUS_TRYAGAIN)
156         all_tryagain = false;
157
158       /* This is really only for debugging.  */
159       if (NSS_STATUS_TRYAGAIN > status || status > NSS_STATUS_RETURN)
160         __libc_fatal ("illegal status in internal_getgrouplist");
161
162       any_success |= status == NSS_STATUS_SUCCESS;
163
164       if (status != NSS_STATUS_SUCCESS
165           && nss_next_action (nip, status) == NSS_ACTION_RETURN)
166          break;
167
168       if (nip->next == NULL)
169         no_more = -1;
170       else
171         nip = nip->next;
172     }
173
174   bool all_written;
175   ssize_t total;
176   time_t timeout;
177  out:
178   all_written = true;
179   timeout = MAX_TIMEOUT_VALUE;
180   if (!any_success)
181     {
182       /* Nothing found.  Create a negative result record.  */
183       total = sizeof (notfound);
184
185       if (he != NULL && all_tryagain)
186         {
187           /* If we have an old record available but cannot find one now
188              because the service is not available we keep the old record
189              and make sure it does not get removed.  */
190           if (reload_count != UINT_MAX && dh->nreloads == reload_count)
191             /* Do not reset the value if we never not reload the record.  */
192             dh->nreloads = reload_count - 1;
193
194           /* Reload with the same time-to-live value.  */
195           timeout = dh->timeout = time (NULL) + db->postimeout;
196         }
197       else
198         {
199           /* We have no data.  This means we send the standard reply for this
200              case.  */
201           if (fd != -1
202               && TEMP_FAILURE_RETRY (send (fd, &notfound, total,
203                                            MSG_NOSIGNAL)) != total)
204             all_written = false;
205
206           /* If we have a transient error or cannot permanently store
207              the result, so be it.  */
208           if (all_tryagain || __builtin_expect (db->negtimeout == 0, 0))
209             {
210               /* Mark the old entry as obsolete.  */
211               if (dh != NULL)
212                 dh->usable = false;
213             }
214           else if ((dataset = mempool_alloc (db, (sizeof (struct dataset)
215                                                   + req->key_len), 1)) != NULL)
216             {
217               dataset->head.allocsize = sizeof (struct dataset) + req->key_len;
218               dataset->head.recsize = total;
219               dataset->head.notfound = true;
220               dataset->head.nreloads = 0;
221               dataset->head.usable = true;
222
223               /* Compute the timeout time.  */
224               timeout = dataset->head.timeout = time (NULL) + db->negtimeout;
225
226               /* This is the reply.  */
227               memcpy (&dataset->resp, &notfound, total);
228
229               /* Copy the key data.  */
230               char *key_copy = memcpy (dataset->strdata, key, req->key_len);
231
232               /* If necessary, we also propagate the data to disk.  */
233               if (db->persistent)
234                 {
235                   // XXX async OK?
236                   uintptr_t pval = (uintptr_t) dataset & ~pagesize_m1;
237                   msync ((void *) pval,
238                          ((uintptr_t) dataset & pagesize_m1)
239                          + sizeof (struct dataset) + req->key_len, MS_ASYNC);
240                 }
241
242               (void) cache_add (req->type, key_copy, req->key_len,
243                                 &dataset->head, true, db, uid, he == NULL);
244
245               pthread_rwlock_unlock (&db->lock);
246
247               /* Mark the old entry as obsolete.  */
248               if (dh != NULL)
249                 dh->usable = false;
250             }
251         }
252     }
253   else
254     {
255
256       total = offsetof (struct dataset, strdata) + start * sizeof (int32_t);
257
258       /* If we refill the cache, first assume the reconrd did not
259          change.  Allocate memory on the cache since it is likely
260          discarded anyway.  If it turns out to be necessary to have a
261          new record we can still allocate real memory.  */
262       bool alloca_used = false;
263       dataset = NULL;
264
265       if (he == NULL)
266         dataset = (struct dataset *) mempool_alloc (db, total + req->key_len,
267                                                     1);
268
269       if (dataset == NULL)
270         {
271           /* We cannot permanently add the result in the moment.  But
272              we can provide the result as is.  Store the data in some
273              temporary memory.  */
274           dataset = (struct dataset *) alloca (total + req->key_len);
275
276           /* We cannot add this record to the permanent database.  */
277           alloca_used = true;
278         }
279
280       dataset->head.allocsize = total + req->key_len;
281       dataset->head.recsize = total - offsetof (struct dataset, resp);
282       dataset->head.notfound = false;
283       dataset->head.nreloads = he == NULL ? 0 : (dh->nreloads + 1);
284       dataset->head.usable = true;
285
286       /* Compute the timeout time.  */
287       timeout = dataset->head.timeout = time (NULL) + db->postimeout;
288
289       dataset->resp.version = NSCD_VERSION;
290       dataset->resp.found = 1;
291       dataset->resp.ngrps = start;
292
293       char *cp = dataset->strdata;
294
295       /* Copy the GID values.  If the size of the types match this is
296          very simple.  */
297       if (sizeof (gid_t) == sizeof (int32_t))
298         cp = mempcpy (cp, groups, start * sizeof (gid_t));
299       else
300         {
301           gid_t *gcp = (gid_t *) cp;
302
303           for (int i = 0; i < start; ++i)
304             *gcp++ = groups[i];
305
306           cp = (char *) gcp;
307         }
308
309       /* Finally the user name.  */
310       memcpy (cp, key, req->key_len);
311
312       assert (cp == dataset->strdata + total - offsetof (struct dataset,
313                                                          strdata));
314
315       /* Now we can determine whether on refill we have to create a new
316          record or not.  */
317       if (he != NULL)
318         {
319           assert (fd == -1);
320
321           if (total + req->key_len == dh->allocsize
322               && total - offsetof (struct dataset, resp) == dh->recsize
323               && memcmp (&dataset->resp, dh->data,
324                          dh->allocsize - offsetof (struct dataset, resp)) == 0)
325             {
326               /* The data has not changed.  We will just bump the
327                  timeout value.  Note that the new record has been
328                  allocated on the stack and need not be freed.  */
329               dh->timeout = dataset->head.timeout;
330               ++dh->nreloads;
331             }
332           else
333             {
334               /* We have to create a new record.  Just allocate
335                  appropriate memory and copy it.  */
336               struct dataset *newp
337                 = (struct dataset *) mempool_alloc (db, total + req->key_len,
338                                                     1);
339               if (newp != NULL)
340                 {
341                   /* Adjust pointer into the memory block.  */
342                   cp = (char *) newp + (cp - (char *) dataset);
343
344                   dataset = memcpy (newp, dataset, total + req->key_len);
345                   alloca_used = false;
346                 }
347
348               /* Mark the old record as obsolete.  */
349               dh->usable = false;
350             }
351         }
352       else
353         {
354           /* We write the dataset before inserting it to the database
355              since while inserting this thread might block and so would
356              unnecessarily let the receiver wait.  */
357           assert (fd != -1);
358
359 #ifdef HAVE_SENDFILE
360           if (__builtin_expect (db->mmap_used, 1) && !alloca_used)
361             {
362               assert (db->wr_fd != -1);
363               assert ((char *) &dataset->resp > (char *) db->data);
364               assert ((char *) dataset - (char *) db->head
365                       + total
366                       <= (sizeof (struct database_pers_head)
367                           + db->head->module * sizeof (ref_t)
368                           + db->head->data_size));
369               ssize_t written = sendfileall (fd, db->wr_fd,
370                                              (char *) &dataset->resp
371                                              - (char *) db->head,
372                                              dataset->head.recsize);
373               if (written != dataset->head.recsize)
374                 {
375 # ifndef __ASSUME_SENDFILE
376                   if (written == -1 && errno == ENOSYS)
377                     goto use_write;
378 # endif
379                   all_written = false;
380                 }
381             }
382           else
383 # ifndef __ASSUME_SENDFILE
384           use_write:
385 # endif
386 #endif
387             if (writeall (fd, &dataset->resp, dataset->head.recsize)
388                 != dataset->head.recsize)
389               all_written = false;
390         }
391
392
393       /* Add the record to the database.  But only if it has not been
394          stored on the stack.  */
395       if (! alloca_used)
396         {
397           /* If necessary, we also propagate the data to disk.  */
398           if (db->persistent)
399             {
400               // XXX async OK?
401               uintptr_t pval = (uintptr_t) dataset & ~pagesize_m1;
402               msync ((void *) pval,
403                      ((uintptr_t) dataset & pagesize_m1) + total +
404                      req->key_len, MS_ASYNC);
405             }
406
407           (void) cache_add (INITGROUPS, cp, req->key_len, &dataset->head, true,
408                             db, uid, he == NULL);
409
410           pthread_rwlock_unlock (&db->lock);
411         }
412     }
413
414   free (groups);
415
416   if (__builtin_expect (!all_written, 0) && debug_level > 0)
417     {
418       char buf[256];
419       dbg_log (_("short write in %s: %s"), __FUNCTION__,
420                strerror_r (errno, buf, sizeof (buf)));
421     }
422
423   return timeout;
424 }
425
426
427 void
428 addinitgroups (struct database_dyn *db, int fd, request_header *req, void *key,
429                uid_t uid)
430 {
431   addinitgroupsX (db, fd, req, key, uid, NULL, NULL);
432 }
433
434
435 time_t
436 readdinitgroups (struct database_dyn *db, struct hashentry *he,
437                  struct datahead *dh)
438 {
439   request_header req =
440     {
441       .type = INITGROUPS,
442       .key_len = he->len
443     };
444
445   return addinitgroupsX (db, -1, &req, db->data + he->key, he->owner, he, dh);
446 }