Define preserve_capabilities and install_real_capabilities.
[kopensolaris-gnu/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
111   /* This is temporary memory, we need not (ad 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 (__builtin_expect (groups == NULL, 0))
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       if (status != NSS_STATUS_SUCCESS
162           && nss_next_action (nip, status) == NSS_ACTION_RETURN)
163          break;
164
165       if (nip->next == NULL)
166         no_more = -1;
167       else
168         nip = nip->next;
169     }
170
171   ssize_t total;
172   ssize_t written;
173  out:
174   if (start == 0)
175     {
176       /* Nothing found.  Create a negative result record.  */
177       written = total = sizeof (notfound);
178
179       if (he != NULL && all_tryagain)
180         {
181           /* If we have an old record available but cannot find one now
182              because the service is not available we keep the old record
183              and make sure it does not get removed.  */
184           if (reload_count != UINT_MAX && dh->nreloads == reload_count)
185             /* Do not reset the value if we never not reload the record.  */
186             dh->nreloads = reload_count - 1;
187         }
188       else
189         {
190           /* We have no data.  This means we send the standard reply for this
191              case.  */
192           if (fd != -1)
193             written = TEMP_FAILURE_RETRY (send (fd, &notfound, total,
194                                                 MSG_NOSIGNAL));
195
196           dataset = mempool_alloc (db, sizeof (struct dataset) + req->key_len);
197           /* If we cannot permanently store the result, so be it.  */
198           if (dataset != NULL)
199             {
200               dataset->head.allocsize = sizeof (struct dataset) + req->key_len;
201               dataset->head.recsize = total;
202               dataset->head.notfound = true;
203               dataset->head.nreloads = 0;
204               dataset->head.usable = true;
205
206               /* Compute the timeout time.  */
207               dataset->head.timeout = time (NULL) + db->negtimeout;
208
209               /* This is the reply.  */
210               memcpy (&dataset->resp, &notfound, total);
211
212               /* Copy the key data.  */
213               char *key_copy = memcpy (dataset->strdata, key, req->key_len);
214
215               /* If necessary, we also propagate the data to disk.  */
216               if (db->persistent)
217                 {
218                   // XXX async OK?
219                   uintptr_t pval = (uintptr_t) dataset & ~pagesize_m1;
220                   msync ((void *) pval,
221                          ((uintptr_t) dataset & pagesize_m1)
222                          + sizeof (struct dataset) + req->key_len, MS_ASYNC);
223                 }
224
225               /* Now get the lock to safely insert the records.  */
226               pthread_rwlock_rdlock (&db->lock);
227
228               if (cache_add (req->type, key_copy, req->key_len,
229                              &dataset->head, true, db, uid) < 0)
230                 /* Ensure the data can be recovered.  */
231                 dataset->head.usable = false;
232
233               pthread_rwlock_unlock (&db->lock);
234
235               /* Mark the old entry as obsolete.  */
236               if (dh != NULL)
237                 dh->usable = false;
238             }
239           else
240             ++db->head->addfailed;
241         }
242     }
243   else
244     {
245
246       written = total = sizeof (struct dataset) + start * sizeof (int32_t);
247
248       /* If we refill the cache, first assume the reconrd did not
249          change.  Allocate memory on the cache since it is likely
250          discarded anyway.  If it turns out to be necessary to have a
251          new record we can still allocate real memory.  */
252       bool alloca_used = false;
253       dataset = NULL;
254
255       if (he == NULL)
256         {
257           dataset = (struct dataset *) mempool_alloc (db,
258                                                       total + req->key_len);
259           if (dataset == NULL)
260             ++db->head->addfailed;
261         }
262
263       if (dataset == NULL)
264         {
265           /* We cannot permanently add the result in the moment.  But
266              we can provide the result as is.  Store the data in some
267              temporary memory.  */
268           dataset = (struct dataset *) alloca (total + req->key_len);
269
270           /* We cannot add this record to the permanent database.  */
271           alloca_used = true;
272         }
273
274       dataset->head.allocsize = total + req->key_len;
275       dataset->head.recsize = total - offsetof (struct dataset, resp);
276       dataset->head.notfound = false;
277       dataset->head.nreloads = he == NULL ? 0 : (dh->nreloads + 1);
278       dataset->head.usable = true;
279
280       /* Compute the timeout time.  */
281       dataset->head.timeout = time (NULL) + db->postimeout;
282
283       dataset->resp.version = NSCD_VERSION;
284       dataset->resp.found = 1;
285       dataset->resp.ngrps = start;
286
287       char *cp = dataset->strdata;
288
289       /* Copy the GID values.  If the size of the types match this is
290          very simple.  */
291       if (sizeof (gid_t) == sizeof (int32_t))
292         cp = mempcpy (cp, groups, start * sizeof (gid_t));
293       else
294         {
295           gid_t *gcp = (gid_t *) cp;
296
297           for (int i = 0; i < start; ++i)
298             *gcp++ = groups[i];
299
300           cp = (char *) gcp;
301         }
302
303       /* Finally the user name.  */
304       memcpy (cp, key, req->key_len);
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               if (newp != NULL)
330                 {
331                   /* Adjust pointer into the memory block.  */
332                   cp = (char *) newp + (cp - (char *) dataset);
333
334                   dataset = memcpy (newp, dataset, total + req->key_len);
335                   alloca_used = false;
336                 }
337
338               /* Mark the old record as obsolete.  */
339               dh->usable = false;
340             }
341         }
342       else
343         {
344           /* We write the dataset before inserting it to the database
345              since while inserting this thread might block and so would
346              unnecessarily let the receiver wait.  */
347           assert (fd != -1);
348
349 #ifdef HAVE_SENDFILE
350           if (__builtin_expect (db->mmap_used, 1) && !alloca_used)
351             {
352               assert (db->wr_fd != -1);
353               assert ((char *) &dataset->resp > (char *) db->data);
354               assert ((char *) &dataset->resp - (char *) db->head
355                       + total
356                       <= (sizeof (struct database_pers_head)
357                           + db->head->module * sizeof (ref_t)
358                           + db->head->data_size));
359               written = sendfileall (fd, db->wr_fd,
360                                      (char *) &dataset->resp
361                                      - (char *) db->head, total);
362 # ifndef __ASSUME_SENDFILE
363               if (written == -1 && errno == ENOSYS)
364                 goto use_write;
365 # endif
366             }
367           else
368 # ifndef __ASSUME_SENDFILE
369           use_write:
370 # endif
371 #endif
372             written = writeall (fd, &dataset->resp, total);
373         }
374
375
376       /* Add the record to the database.  But only if it has not been
377          stored on the stack.  */
378       if (! alloca_used)
379         {
380           /* If necessary, we also propagate the data to disk.  */
381           if (db->persistent)
382             {
383               // XXX async OK?
384               uintptr_t pval = (uintptr_t) dataset & ~pagesize_m1;
385               msync ((void *) pval,
386                      ((uintptr_t) dataset & pagesize_m1) + total +
387                      req->key_len, MS_ASYNC);
388             }
389
390           /* Now get the lock to safely insert the records.  */
391           pthread_rwlock_rdlock (&db->lock);
392
393           if (cache_add (INITGROUPS, cp, req->key_len, &dataset->head, true,
394                          db, uid) < 0)
395             /* Could not allocate memory.  Make sure the data gets
396                discarded.  */
397             dataset->head.usable = false;
398
399           pthread_rwlock_unlock (&db->lock);
400         }
401     }
402
403   free (groups);
404
405   if (__builtin_expect (written != total, 0) && debug_level > 0)
406     {
407       char buf[256];
408       dbg_log (_("short write in %s: %s"), __FUNCTION__,
409                strerror_r (errno, buf, sizeof (buf)));
410     }
411 }
412
413
414 void
415 addinitgroups (struct database_dyn *db, int fd, request_header *req, void *key,
416                uid_t uid)
417 {
418   addinitgroupsX (db, fd, req, key, uid, NULL, NULL);
419 }
420
421
422 void
423 readdinitgroups (struct database_dyn *db, struct hashentry *he,
424                  struct datahead *dh)
425 {
426   request_header req =
427     {
428       .type = INITGROUPS,
429       .key_len = he->len
430     };
431
432   addinitgroupsX (db, -1, &req, db->data + he->key, he->owner, he, dh);
433 }