[BZ #3273]
[platform/upstream/glibc.git] / nscd / initgrcache.c
1 /* Cache handling for host lookup.
2    Copyright (C) 2004, 2005, 2006 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 version 2 as
8    published by the Free Software Foundation.
9
10    This program is distributed in the hope that it will be useful,
11    but WITHOUT ANY WARRANTY; without even the implied warranty of
12    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13    GNU General Public License for more details.
14
15    You should have received a copy of the GNU General Public License
16    along with this program; if not, write to the Free Software Foundation,
17    Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.  */
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 void
55 addinitgroupsX (struct database_dyn *db, int fd, request_header *req,
56                 void *key, uid_t uid, struct hashentry *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 (ad 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   ssize_t total;
175   ssize_t written;
176  out:
177   if (!any_success)
178     {
179       /* Nothing found.  Create a negative result record.  */
180       written = total = sizeof (notfound);
181
182       if (he != NULL && all_tryagain)
183         {
184           /* If we have an old record available but cannot find one now
185              because the service is not available we keep the old record
186              and make sure it does not get removed.  */
187           if (reload_count != UINT_MAX && dh->nreloads == reload_count)
188             /* Do not reset the value if we never not reload the record.  */
189             dh->nreloads = reload_count - 1;
190         }
191       else
192         {
193           /* We have no data.  This means we send the standard reply for this
194              case.  */
195           if (fd != -1)
196             written = TEMP_FAILURE_RETRY (send (fd, &notfound, total,
197                                                 MSG_NOSIGNAL));
198
199           dataset = mempool_alloc (db, sizeof (struct dataset) + req->key_len);
200           /* If we cannot permanently store the result, so be it.  */
201           if (dataset != NULL)
202             {
203               dataset->head.allocsize = sizeof (struct dataset) + req->key_len;
204               dataset->head.recsize = total;
205               dataset->head.notfound = true;
206               dataset->head.nreloads = 0;
207               dataset->head.usable = true;
208
209               /* Compute the timeout time.  */
210               dataset->head.timeout = time (NULL) + db->negtimeout;
211
212               /* This is the reply.  */
213               memcpy (&dataset->resp, &notfound, total);
214
215               /* Copy the key data.  */
216               char *key_copy = memcpy (dataset->strdata, key, req->key_len);
217
218               /* If necessary, we also propagate the data to disk.  */
219               if (db->persistent)
220                 {
221                   // XXX async OK?
222                   uintptr_t pval = (uintptr_t) dataset & ~pagesize_m1;
223                   msync ((void *) pval,
224                          ((uintptr_t) dataset & pagesize_m1)
225                          + sizeof (struct dataset) + req->key_len, MS_ASYNC);
226                 }
227
228               /* Now get the lock to safely insert the records.  */
229               pthread_rwlock_rdlock (&db->lock);
230
231               if (cache_add (req->type, key_copy, req->key_len,
232                              &dataset->head, true, db, uid) < 0)
233                 /* Ensure the data can be recovered.  */
234                 dataset->head.usable = false;
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           if (dataset == NULL)
263             ++db->head->addfailed;
264         }
265
266       if (dataset == NULL)
267         {
268           /* We cannot permanently add the result in the moment.  But
269              we can provide the result as is.  Store the data in some
270              temporary memory.  */
271           dataset = (struct dataset *) alloca (total + req->key_len);
272
273           /* We cannot add this record to the permanent database.  */
274           alloca_used = true;
275         }
276
277       dataset->head.allocsize = total + req->key_len;
278       dataset->head.recsize = total - offsetof (struct dataset, resp);
279       dataset->head.notfound = false;
280       dataset->head.nreloads = he == NULL ? 0 : (dh->nreloads + 1);
281       dataset->head.usable = true;
282
283       /* Compute the timeout time.  */
284       dataset->head.timeout = time (NULL) + db->postimeout;
285
286       dataset->resp.version = NSCD_VERSION;
287       dataset->resp.found = 1;
288       dataset->resp.ngrps = start;
289
290       char *cp = dataset->strdata;
291
292       /* Copy the GID values.  If the size of the types match this is
293          very simple.  */
294       if (sizeof (gid_t) == sizeof (int32_t))
295         cp = mempcpy (cp, groups, start * sizeof (gid_t));
296       else
297         {
298           gid_t *gcp = (gid_t *) cp;
299
300           for (int i = 0; i < start; ++i)
301             *gcp++ = groups[i];
302
303           cp = (char *) gcp;
304         }
305
306       /* Finally the user name.  */
307       memcpy (cp, key, req->key_len);
308
309       /* Now we can determine whether on refill we have to create a new
310          record or not.  */
311       if (he != NULL)
312         {
313           assert (fd == -1);
314
315           if (total + req->key_len == dh->allocsize
316               && total - offsetof (struct dataset, resp) == dh->recsize
317               && memcmp (&dataset->resp, dh->data,
318                          dh->allocsize - offsetof (struct dataset, resp)) == 0)
319             {
320               /* The data has not changed.  We will just bump the
321                  timeout value.  Note that the new record has been
322                  allocated on the stack and need not be freed.  */
323               dh->timeout = dataset->head.timeout;
324               ++dh->nreloads;
325             }
326           else
327             {
328               /* We have to create a new record.  Just allocate
329                  appropriate memory and copy it.  */
330               struct dataset *newp
331                 = (struct dataset *) mempool_alloc (db, total + req->key_len);
332               if (newp != NULL)
333                 {
334                   /* Adjust pointer into the memory block.  */
335                   cp = (char *) newp + (cp - (char *) dataset);
336
337                   dataset = memcpy (newp, dataset, total + req->key_len);
338                   alloca_used = false;
339                 }
340
341               /* Mark the old record as obsolete.  */
342               dh->usable = false;
343             }
344         }
345       else
346         {
347           /* We write the dataset before inserting it to the database
348              since while inserting this thread might block and so would
349              unnecessarily let the receiver wait.  */
350           assert (fd != -1);
351
352 #ifdef HAVE_SENDFILE
353           if (__builtin_expect (db->mmap_used, 1) && !alloca_used)
354             {
355               assert (db->wr_fd != -1);
356               assert ((char *) &dataset->resp > (char *) db->data);
357               assert ((char *) &dataset->resp - (char *) db->head
358                       + total
359                       <= (sizeof (struct database_pers_head)
360                           + db->head->module * sizeof (ref_t)
361                           + db->head->data_size));
362               written = sendfileall (fd, db->wr_fd,
363                                      (char *) &dataset->resp
364                                      - (char *) db->head, total);
365 # ifndef __ASSUME_SENDFILE
366               if (written == -1 && errno == ENOSYS)
367                 goto use_write;
368 # endif
369             }
370           else
371 # ifndef __ASSUME_SENDFILE
372           use_write:
373 # endif
374 #endif
375             written = writeall (fd, &dataset->resp, total);
376         }
377
378
379       /* Add the record to the database.  But only if it has not been
380          stored on the stack.  */
381       if (! alloca_used)
382         {
383           /* If necessary, we also propagate the data to disk.  */
384           if (db->persistent)
385             {
386               // XXX async OK?
387               uintptr_t pval = (uintptr_t) dataset & ~pagesize_m1;
388               msync ((void *) pval,
389                      ((uintptr_t) dataset & pagesize_m1) + total +
390                      req->key_len, MS_ASYNC);
391             }
392
393           /* Now get the lock to safely insert the records.  */
394           pthread_rwlock_rdlock (&db->lock);
395
396           if (cache_add (INITGROUPS, cp, req->key_len, &dataset->head, true,
397                          db, uid) < 0)
398             /* Could not allocate memory.  Make sure the data gets
399                discarded.  */
400             dataset->head.usable = false;
401
402           pthread_rwlock_unlock (&db->lock);
403         }
404     }
405
406   free (groups);
407
408   if (__builtin_expect (written != total, 0) && debug_level > 0)
409     {
410       char buf[256];
411       dbg_log (_("short write in %s: %s"), __FUNCTION__,
412                strerror_r (errno, buf, sizeof (buf)));
413     }
414 }
415
416
417 void
418 addinitgroups (struct database_dyn *db, int fd, request_header *req, void *key,
419                uid_t uid)
420 {
421   addinitgroupsX (db, fd, req, key, uid, NULL, NULL);
422 }
423
424
425 void
426 readdinitgroups (struct database_dyn *db, struct hashentry *he,
427                  struct datahead *dh)
428 {
429   request_header req =
430     {
431       .type = INITGROUPS,
432       .key_len = he->len
433     };
434
435   addinitgroupsX (db, -1, &req, db->data + he->key, he->owner, he, dh);
436 }