Remove Linux kernel version ambiguity in comment added by previous commit.
[platform/upstream/glibc.git] / nscd / netgroupcache.c
1 /* Cache handling for netgroup lookup.
2    Copyright (C) 2011-2013 Free Software Foundation, Inc.
3    This file is part of the GNU C Library.
4    Contributed by Ulrich Drepper <drepper@gmail.com>, 2011.
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 <alloca.h>
20 #include <assert.h>
21 #include <errno.h>
22 #include <libintl.h>
23 #include <stdbool.h>
24 #include <unistd.h>
25 #include <sys/mman.h>
26
27 #include "../inet/netgroup.h"
28 #include "nscd.h"
29 #include "dbg_log.h"
30
31 #include <kernel-features.h>
32
33
34 /* This is the standard reply in case the service is disabled.  */
35 static const netgroup_response_header disabled =
36 {
37   .version = NSCD_VERSION,
38   .found = -1,
39   .nresults = 0,
40   .result_len = 0
41 };
42
43 /* This is the struct describing how to write this record.  */
44 const struct iovec netgroup_iov_disabled =
45 {
46   .iov_base = (void *) &disabled,
47   .iov_len = sizeof (disabled)
48 };
49
50
51 /* This is the standard reply in case we haven't found the dataset.  */
52 static const netgroup_response_header notfound =
53 {
54   .version = NSCD_VERSION,
55   .found = 0,
56   .nresults = 0,
57   .result_len = 0
58 };
59
60
61 struct dataset
62 {
63   struct datahead head;
64   netgroup_response_header resp;
65   char strdata[0];
66 };
67
68
69 static time_t
70 addgetnetgrentX (struct database_dyn *db, int fd, request_header *req,
71                  const char *key, uid_t uid, struct hashentry *he,
72                  struct datahead *dh, struct dataset **resultp)
73 {
74   if (__builtin_expect (debug_level > 0, 0))
75     {
76       if (he == NULL)
77         dbg_log (_("Haven't found \"%s\" in netgroup cache!"), key);
78       else
79         dbg_log (_("Reloading \"%s\" in netgroup cache!"), key);
80     }
81
82   static service_user *netgroup_database;
83   time_t timeout;
84   struct dataset *dataset;
85   bool cacheable = false;
86   ssize_t total;
87
88   char *key_copy = NULL;
89   struct __netgrent data;
90   size_t buflen = MAX (1024, sizeof (*dataset) + req->key_len);
91   size_t buffilled = sizeof (*dataset);
92   char *buffer = NULL;
93   size_t nentries = 0;
94   bool use_malloc = false;
95   size_t group_len = strlen (key) + 1;
96   union
97   {
98     struct name_list elem;
99     char mem[sizeof (struct name_list) + group_len];
100   } first_needed;
101
102   if (netgroup_database == NULL
103       && __nss_database_lookup ("netgroup", NULL, NULL, &netgroup_database))
104     {
105       /* No such service.  */
106       total = sizeof (notfound);
107       timeout = time (NULL) + db->negtimeout;
108
109       if (fd != -1)
110         TEMP_FAILURE_RETRY (send (fd, &notfound, total, MSG_NOSIGNAL));
111
112       dataset = mempool_alloc (db, sizeof (struct dataset) + req->key_len, 1);
113       /* If we cannot permanently store the result, so be it.  */
114       if (dataset != NULL)
115         {
116           dataset->head.allocsize = sizeof (struct dataset) + req->key_len;
117           dataset->head.recsize = total;
118           dataset->head.notfound = true;
119           dataset->head.nreloads = 0;
120           dataset->head.usable = true;
121
122           /* Compute the timeout time.  */
123           timeout = dataset->head.timeout = time (NULL) + db->negtimeout;
124           dataset->head.ttl = db->negtimeout;
125
126           /* This is the reply.  */
127           memcpy (&dataset->resp, &notfound, total);
128
129           /* Copy the key data.  */
130           memcpy (dataset->strdata, key, req->key_len);
131
132           cacheable = true;
133         }
134
135       goto writeout;
136     }
137
138   memset (&data, '\0', sizeof (data));
139   buffer = alloca (buflen);
140   first_needed.elem.next = &first_needed.elem;
141   memcpy (first_needed.elem.name, key, group_len);
142   data.needed_groups = &first_needed.elem;
143
144   while (data.needed_groups != NULL)
145     {
146       /* Add the next group to the list of those which are known.  */
147       struct name_list *this_group = data.needed_groups->next;
148       if (this_group == data.needed_groups)
149         data.needed_groups = NULL;
150       else
151         data.needed_groups->next = this_group->next;
152       this_group->next = data.known_groups;
153       data.known_groups = this_group;
154
155       union
156       {
157         enum nss_status (*f) (const char *, struct __netgrent *);
158         void *ptr;
159       } setfct;
160
161       service_user *nip = netgroup_database;
162       int no_more = __nss_lookup (&nip, "setnetgrent", NULL, &setfct.ptr);
163       while (!no_more)
164         {
165           enum nss_status status
166             = DL_CALL_FCT (*setfct.f, (data.known_groups->name, &data));
167
168           if (status == NSS_STATUS_SUCCESS)
169             {
170               union
171               {
172                 enum nss_status (*f) (struct __netgrent *, char *, size_t,
173                                       int *);
174                 void *ptr;
175               } getfct;
176               getfct.ptr = __nss_lookup_function (nip, "getnetgrent_r");
177               if (getfct.f != NULL)
178                 while (1)
179                   {
180                     int e;
181                     status = getfct.f (&data, buffer + buffilled,
182                                        buflen - buffilled, &e);
183                     if (status == NSS_STATUS_RETURN)
184                       /* This was the last one for this group.  Look
185                          at next group if available.  */
186                       break;
187                     if (status == NSS_STATUS_SUCCESS)
188                       {
189                         if (data.type == triple_val)
190                           {
191                             const char *nhost = data.val.triple.host;
192                             const char *nuser = data.val.triple.user;
193                             const char *ndomain = data.val.triple.domain;
194
195                             if (nhost == NULL || nuser == NULL || ndomain == NULL
196                                 || nhost > nuser || nuser > ndomain)
197                               {
198                                 const char *last = nhost;
199                                 if (last == NULL
200                                     || (nuser != NULL && nuser > last))
201                                   last = nuser;
202                                 if (last == NULL
203                                     || (ndomain != NULL && ndomain > last))
204                                   last = ndomain;
205
206                                 size_t bufused
207                                   = (last == NULL
208                                      ? buffilled
209                                      : last + strlen (last) + 1 - buffer);
210
211                                 /* We have to make temporary copies.  */
212                                 size_t hostlen = strlen (nhost ?: "") + 1;
213                                 size_t userlen = strlen (nuser ?: "") + 1;
214                                 size_t domainlen = strlen (ndomain ?: "") + 1;
215                                 size_t needed = hostlen + userlen + domainlen;
216
217                                 if (buflen - req->key_len - bufused < needed)
218                                   {
219                                     size_t newsize = MAX (2 * buflen,
220                                                           buflen + 2 * needed);
221                                     if (use_malloc || newsize > 1024 * 1024)
222                                       {
223                                         buflen = newsize;
224                                         char *newbuf = xrealloc (use_malloc
225                                                                  ? buffer
226                                                                  : NULL,
227                                                                  buflen);
228
229                                         buffer = newbuf;
230                                         use_malloc = true;
231                                       }
232                                     else
233                                       extend_alloca (buffer, buflen, newsize);
234                                   }
235
236                                 nhost = memcpy (buffer + bufused,
237                                                 nhost ?: "", hostlen);
238                                 nuser = memcpy ((char *) nhost + hostlen,
239                                                 nuser ?: "", userlen);
240                                 ndomain = memcpy ((char *) nuser + userlen,
241                                                   ndomain ?: "", domainlen);
242                               }
243
244                             char *wp = buffer + buffilled;
245                             wp = stpcpy (wp, nhost) + 1;
246                             wp = stpcpy (wp, nuser) + 1;
247                             wp = stpcpy (wp, ndomain) + 1;
248                             buffilled = wp - buffer;
249                             ++nentries;
250                           }
251                         else
252                           {
253                             /* Check that the group has not been
254                                requested before.  */
255                             struct name_list *runp = data.needed_groups;
256                             if (runp != NULL)
257                               while (1)
258                                 {
259                                   if (strcmp (runp->name, data.val.group) == 0)
260                                     break;
261
262                                   runp = runp->next;
263                                   if (runp == data.needed_groups)
264                                     {
265                                       runp = NULL;
266                                       break;
267                                     }
268                                 }
269
270                             if (runp == NULL)
271                               {
272                                 runp = data.known_groups;
273                                 while (runp != NULL)
274                                   if (strcmp (runp->name, data.val.group) == 0)
275                                     break;
276                                   else
277                                     runp = runp->next;
278                                 }
279
280                             if (runp == NULL)
281                               {
282                                 /* A new group is requested.  */
283                                 size_t namelen = strlen (data.val.group) + 1;
284                                 struct name_list *newg = alloca (sizeof (*newg)
285                                                                  + namelen);
286                                 memcpy (newg->name, data.val.group, namelen);
287                                 if (data.needed_groups == NULL)
288                                   data.needed_groups = newg->next = newg;
289                                 else
290                                   {
291                                     newg->next = data.needed_groups->next;
292                                     data.needed_groups->next = newg;
293                                     data.needed_groups = newg;
294                                   }
295                               }
296                           }
297                       }
298                     else if (status == NSS_STATUS_UNAVAIL && e == ERANGE)
299                       {
300                         size_t newsize = 2 * buflen;
301                         if (use_malloc || newsize > 1024 * 1024)
302                           {
303                             buflen = newsize;
304                             char *newbuf = xrealloc (use_malloc
305                                                      ? buffer : NULL, buflen);
306
307                             buffer = newbuf;
308                             use_malloc = true;
309                           }
310                         else
311                           extend_alloca (buffer, buflen, newsize);
312                       }
313                   }
314
315               enum nss_status (*endfct) (struct __netgrent *);
316               endfct = __nss_lookup_function (nip, "endnetgrent");
317               if (endfct != NULL)
318                 (void) DL_CALL_FCT (*endfct, (&data));
319
320               break;
321             }
322
323           no_more = __nss_next2 (&nip, "setnetgrent", NULL, &setfct.ptr,
324                                  status, 0);
325         }
326     }
327
328   total = buffilled;
329
330   /* Fill in the dataset.  */
331   dataset = (struct dataset *) buffer;
332   dataset->head.allocsize = total + req->key_len;
333   dataset->head.recsize = total - offsetof (struct dataset, resp);
334   dataset->head.notfound = false;
335   dataset->head.nreloads = he == NULL ? 0 : (dh->nreloads + 1);
336   dataset->head.usable = true;
337   dataset->head.ttl = db->postimeout;
338   timeout = dataset->head.timeout = time (NULL) + dataset->head.ttl;
339
340   dataset->resp.version = NSCD_VERSION;
341   dataset->resp.found = 1;
342   dataset->resp.nresults = nentries;
343   dataset->resp.result_len = buffilled - sizeof (*dataset);
344
345   assert (buflen - buffilled >= req->key_len);
346   key_copy = memcpy (buffer + buffilled, key, req->key_len);
347   buffilled += req->key_len;
348
349   /* Now we can determine whether on refill we have to create a new
350      record or not.  */
351   if (he != NULL)
352     {
353       assert (fd == -1);
354
355       if (dataset->head.allocsize == dh->allocsize
356           && dataset->head.recsize == dh->recsize
357           && memcmp (&dataset->resp, dh->data,
358                      dh->allocsize - offsetof (struct dataset, resp)) == 0)
359         {
360           /* The data has not changed.  We will just bump the timeout
361              value.  Note that the new record has been allocated on
362              the stack and need not be freed.  */
363           dh->timeout = dataset->head.timeout;
364           dh->ttl = dataset->head.ttl;
365           ++dh->nreloads;
366           dataset = (struct dataset *) dh;
367
368           goto out;
369         }
370     }
371
372   {
373     struct dataset *newp
374       = (struct dataset *) mempool_alloc (db, total + req->key_len, 1);
375     if (__builtin_expect (newp != NULL, 1))
376       {
377         /* Adjust pointer into the memory block.  */
378         key_copy = (char *) newp + (key_copy - buffer);
379
380         dataset = memcpy (newp, dataset, total + req->key_len);
381         cacheable = true;
382
383         if (he != NULL)
384           /* Mark the old record as obsolete.  */
385           dh->usable = false;
386       }
387   }
388
389   if (he == NULL && fd != -1)
390     {
391       /* We write the dataset before inserting it to the database
392          since while inserting this thread might block and so would
393          unnecessarily let the receiver wait.  */
394     writeout:
395 #ifdef HAVE_SENDFILE
396       if (__builtin_expect (db->mmap_used, 1) && cacheable)
397         {
398           assert (db->wr_fd != -1);
399           assert ((char *) &dataset->resp > (char *) db->data);
400           assert ((char *) dataset - (char *) db->head + total
401                   <= (sizeof (struct database_pers_head)
402                       + db->head->module * sizeof (ref_t)
403                       + db->head->data_size));
404 # ifndef __ASSUME_SENDFILE
405           ssize_t written =
406 # endif
407             sendfileall (fd, db->wr_fd, (char *) &dataset->resp
408                          - (char *) db->head, dataset->head.recsize);
409 # ifndef __ASSUME_SENDFILE
410           if (written == -1 && errno == ENOSYS)
411             goto use_write;
412 # endif
413         }
414       else
415 #endif
416         {
417 #if defined HAVE_SENDFILE && !defined __ASSUME_SENDFILE
418         use_write:
419 #endif
420           writeall (fd, &dataset->resp, dataset->head.recsize);
421         }
422     }
423
424   if (cacheable)
425     {
426       /* If necessary, we also propagate the data to disk.  */
427       if (db->persistent)
428         {
429           // XXX async OK?
430           uintptr_t pval = (uintptr_t) dataset & ~pagesize_m1;
431           msync ((void *) pval,
432                  ((uintptr_t) dataset & pagesize_m1) + total + req->key_len,
433                  MS_ASYNC);
434         }
435
436       (void) cache_add (req->type, key_copy, req->key_len, &dataset->head,
437                         true, db, uid, he == NULL);
438
439       pthread_rwlock_unlock (&db->lock);
440
441       /* Mark the old entry as obsolete.  */
442       if (dh != NULL)
443         dh->usable = false;
444     }
445
446  out:
447   if (use_malloc)
448     free (buffer);
449
450   *resultp = dataset;
451
452   return timeout;
453 }
454
455
456 static time_t
457 addinnetgrX (struct database_dyn *db, int fd, request_header *req,
458              char *key, uid_t uid, struct hashentry *he,
459              struct datahead *dh)
460 {
461   const char *group = key;
462   key = (char *) rawmemchr (key, '\0') + 1;
463   size_t group_len = key - group - 1;
464   const char *host = *key++ ? key : NULL;
465   if (host != NULL)
466     key = (char *) rawmemchr (key, '\0') + 1;
467   const char *user = *key++ ? key : NULL;
468   if (user != NULL)
469     key = (char *) rawmemchr (key, '\0') + 1;
470   const char *domain = *key++ ? key : NULL;
471
472   if (__builtin_expect (debug_level > 0, 0))
473     {
474       if (he == NULL)
475         dbg_log (_("Haven't found \"%s (%s,%s,%s)\" in netgroup cache!"),
476                  group, host ?: "", user ?: "", domain ?: "");
477       else
478         dbg_log (_("Reloading \"%s (%s,%s,%s)\" in netgroup cache!"),
479                  group, host ?: "", user ?: "", domain ?: "");
480     }
481
482   struct dataset *result = (struct dataset *) cache_search (GETNETGRENT,
483                                                             group, group_len,
484                                                             db, uid);
485   time_t timeout;
486   if (result != NULL)
487     timeout = result->head.timeout;
488   else
489     {
490       request_header req_get =
491         {
492           .type = GETNETGRENT,
493           .key_len = group_len
494         };
495       timeout = addgetnetgrentX (db, -1, &req_get, group, uid, NULL, NULL,
496                                  &result);
497     }
498
499   struct indataset
500   {
501     struct datahead head;
502     innetgroup_response_header resp;
503   } *dataset
504       = (struct indataset *) mempool_alloc (db,
505                                             sizeof (*dataset) + req->key_len,
506                                             1);
507   struct indataset dataset_mem;
508   bool cacheable = true;
509   if (__builtin_expect (dataset == NULL, 0))
510     {
511       cacheable = false;
512       dataset = &dataset_mem;
513     }
514
515   dataset->head.allocsize = sizeof (*dataset) + req->key_len;
516   dataset->head.recsize = sizeof (innetgroup_response_header);
517   dataset->head.notfound = result->head.notfound;
518   dataset->head.nreloads = he == NULL ? 0 : (dh->nreloads + 1);
519   dataset->head.usable = true;
520   dataset->head.ttl = result->head.ttl;
521   dataset->head.timeout = timeout;
522
523   dataset->resp.version = NSCD_VERSION;
524   dataset->resp.found = result->resp.found;
525   /* Until we find a matching entry the result is 0.  */
526   dataset->resp.result = 0;
527
528   char *key_copy = memcpy ((char *) (dataset + 1), group, req->key_len);
529
530   if (dataset->resp.found)
531     {
532       const char *triplets = (const char *) (&result->resp + 1);
533
534       for (nscd_ssize_t i = result->resp.nresults; i > 0; --i)
535         {
536           bool success = true;
537
538           if (host != NULL)
539             success = strcmp (host, triplets) == 0;
540           triplets = (const char *) rawmemchr (triplets, '\0') + 1;
541
542           if (success && user != NULL)
543             success = strcmp (user, triplets) == 0;
544           triplets = (const char *) rawmemchr (triplets, '\0') + 1;
545
546           if (success && (domain == NULL || strcmp (domain, triplets) == 0))
547             {
548               dataset->resp.result = 1;
549               break;
550             }
551           triplets = (const char *) rawmemchr (triplets, '\0') + 1;
552         }
553     }
554
555   if (he != NULL && dh->data[0].innetgroupdata.result == dataset->resp.result)
556     {
557       /* The data has not changed.  We will just bump the timeout
558          value.  Note that the new record has been allocated on
559          the stack and need not be freed.  */
560       dh->timeout = timeout;
561       dh->ttl = dataset->head.ttl;
562       ++dh->nreloads;
563       return timeout;
564     }
565
566   if (he == NULL)
567     {
568       /* We write the dataset before inserting it to the database
569          since while inserting this thread might block and so would
570          unnecessarily let the receiver wait.  */
571        assert (fd != -1);
572
573 #ifdef HAVE_SENDFILE
574       if (__builtin_expect (db->mmap_used, 1) && cacheable)
575         {
576           assert (db->wr_fd != -1);
577           assert ((char *) &dataset->resp > (char *) db->data);
578           assert ((char *) dataset - (char *) db->head + sizeof (*dataset)
579                   <= (sizeof (struct database_pers_head)
580                       + db->head->module * sizeof (ref_t)
581                       + db->head->data_size));
582 # ifndef __ASSUME_SENDFILE
583           ssize_t written =
584 # endif
585             sendfileall (fd, db->wr_fd,
586                          (char *) &dataset->resp - (char *) db->head,
587                          sizeof (innetgroup_response_header));
588 # ifndef __ASSUME_SENDFILE
589           if (written == -1 && errno == ENOSYS)
590             goto use_write;
591 # endif
592         }
593       else
594         {
595 # ifndef __ASSUME_SENDFILE
596         use_write:
597 # endif
598 #endif
599           writeall (fd, &dataset->resp, sizeof (innetgroup_response_header));
600         }
601     }
602
603   if (cacheable)
604     {
605       /* If necessary, we also propagate the data to disk.  */
606       if (db->persistent)
607         {
608           // XXX async OK?
609           uintptr_t pval = (uintptr_t) dataset & ~pagesize_m1;
610           msync ((void *) pval,
611                  ((uintptr_t) dataset & pagesize_m1) + sizeof (*dataset)
612                  + req->key_len,
613                  MS_ASYNC);
614         }
615
616       (void) cache_add (req->type, key_copy, req->key_len, &dataset->head,
617                         true, db, uid, he == NULL);
618
619       pthread_rwlock_unlock (&db->lock);
620
621       /* Mark the old entry as obsolete.  */
622       if (dh != NULL)
623         dh->usable = false;
624     }
625
626   return timeout;
627 }
628
629
630 void
631 addgetnetgrent (struct database_dyn *db, int fd, request_header *req,
632                 void *key, uid_t uid)
633 {
634   struct dataset *ignore;
635
636   addgetnetgrentX (db, fd, req, key, uid, NULL, NULL, &ignore);
637 }
638
639
640 time_t
641 readdgetnetgrent (struct database_dyn *db, struct hashentry *he,
642                   struct datahead *dh)
643 {
644   request_header req =
645     {
646       .type = GETNETGRENT,
647       .key_len = he->len
648     };
649   struct dataset *ignore;
650
651   return addgetnetgrentX (db, -1, &req, db->data + he->key, he->owner, he, dh,
652                           &ignore);
653 }
654
655
656 void
657 addinnetgr (struct database_dyn *db, int fd, request_header *req,
658             void *key, uid_t uid)
659 {
660   addinnetgrX (db, fd, req, key, uid, NULL, NULL);
661 }
662
663
664 time_t
665 readdinnetgr (struct database_dyn *db, struct hashentry *he,
666               struct datahead *dh)
667 {
668   request_header req =
669     {
670       .type = INNETGR,
671       .key_len = he->len
672     };
673
674   return addinnetgrX (db, -1, &req, db->data + he->key, he->owner, he, dh);
675 }