* nscd/pwdcache.c (save_pwd): Rewrite to use only one malloc call.
[kopensolaris-gnu/glibc.git] / nscd / grpcache.c
1 /* Copyright (c) 1998 Free Software Foundation, Inc.
2    This file is part of the GNU C Library.
3    Contributed by Thorsten Kukuk <kukuk@vt.uni-paderborn.de>, 1998.
4
5    The GNU C Library is free software; you can redistribute it and/or
6    modify it under the terms of the GNU Library General Public License as
7    published by the Free Software Foundation; either version 2 of the
8    License, or (at your option) any later version.
9
10    The GNU C Library 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 GNU
13    Library General Public License for more details.
14
15    You should have received a copy of the GNU Library General Public
16    License along with the GNU C Library; see the file COPYING.LIB.  If not,
17    write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
18    Boston, MA 02111-1307, USA. */
19
20 #include <errno.h>
21 #include <grp.h>
22 #include <pthread.h>
23 #include <stdlib.h>
24 #include <string.h>
25 #include <unistd.h>
26 #include <rpcsvc/nis.h>
27 #include <sys/types.h>
28
29 #include "dbg_log.h"
30 #include "nscd.h"
31
32 static unsigned long modulo = 211;
33 static unsigned long postimeout = 3600;
34 static unsigned long negtimeout = 60;
35
36 static unsigned long poshit = 0;
37 static unsigned long posmiss = 0;
38 static unsigned long neghit = 0;
39 static unsigned long negmiss = 0;
40
41 struct grphash
42 {
43   time_t create;
44   struct grphash *next;
45   struct group *grp;
46 };
47 typedef struct grphash grphash;
48
49 struct gidhash
50 {
51   struct gidhash *next;
52   struct group *grptr;
53 };
54 typedef struct gidhash gidhash;
55
56 struct neghash
57 {
58   time_t create;
59   struct neghash *next;
60   char *key;
61 };
62 typedef struct neghash neghash;
63
64 static grphash *grptbl;
65 static gidhash *gidtbl;
66 static neghash *negtbl;
67
68 static pthread_rwlock_t grplock = PTHREAD_RWLOCK_INITIALIZER;
69 static pthread_rwlock_t neglock = PTHREAD_RWLOCK_INITIALIZER;
70
71 static void *grptable_update (void *);
72 static void *negtable_update (void *);
73
74 void
75 get_gr_stat (stat_response_header *stat)
76 {
77   stat->gr_poshit = poshit;
78   stat->gr_posmiss = posmiss;
79   stat->gr_neghit = neghit;
80   stat->gr_negmiss = negmiss;
81   stat->gr_size = modulo;
82   stat->gr_posttl = postimeout;
83   stat->gr_negttl = negtimeout;
84 }
85
86 void
87 set_grp_modulo (unsigned long mod)
88 {
89   modulo = mod;
90 }
91
92 void
93 set_pos_grp_ttl (unsigned long ttl)
94 {
95   postimeout = ttl;
96 }
97
98 void
99 set_neg_grp_ttl (unsigned long ttl)
100 {
101   negtimeout = ttl;
102 }
103
104 int
105 cache_grpinit ()
106 {
107   pthread_attr_t attr;
108   pthread_t thread;
109
110   grptbl = calloc (modulo, sizeof (grphash));
111   if (grptbl == NULL)
112     return -1;
113   gidtbl = calloc (modulo, sizeof (grphash));
114   if (gidtbl == NULL)
115     return -1;
116   negtbl = calloc (modulo, sizeof (neghash));
117   if (negtbl == NULL)
118     return -1;
119
120   pthread_attr_init (&attr);
121   pthread_attr_setdetachstate (&attr, PTHREAD_CREATE_DETACHED);
122
123   pthread_create (&thread, NULL, grptable_update, &attr);
124   pthread_create (&thread, NULL, negtable_update, &attr);
125
126   pthread_attr_destroy (&attr);
127
128   return 0;
129 }
130
131 static struct group *
132 save_grp (struct group *src)
133 {
134   struct group *dest;
135   unsigned long int l;
136   size_t tlen;
137   size_t name_len = strlen (src->gr_name) + 1;
138   size_t passwd_len = strlen (src->gr_passwd) + 1;
139   char *cp;
140
141   /* How many members does this group have?  */
142   l = tlen = 0;
143   while (src->gr_mem[l] != NULL)
144     tlen += strlen (src->gr_mem[l++]) + 1;
145
146   dest = malloc (sizeof (struct group) + (l + 1) * sizeof (char *)
147                  + name_len + passwd_len + tlen);
148   if (dest == NULL)
149     return NULL;
150
151   dest->gr_mem = (char **) (dest + 1);
152   cp = (char *) (dest->gr_mem + l + 1);
153
154   dest->gr_name = cp;
155   cp = mempcpy (cp, src->gr_name, name_len);
156   dest->gr_passwd = cp;
157   cp = mempcpy (cp, src->gr_passwd, passwd_len);
158   dest->gr_gid = src->gr_gid;
159
160   l = 0;
161   while (src->gr_mem[l] != NULL)
162     {
163       dest->gr_mem[l] = cp;
164       cp = stpcpy (cp, src->gr_mem[l]);
165       ++l;
166     }
167   dest->gr_mem[l] = NULL;
168
169   return dest;
170 }
171
172 static void
173 free_grp (struct group *src)
174 {
175   free (src);
176 }
177
178 static int
179 add_cache (struct group *grp)
180 {
181   grphash *work;
182   gidhash *gidwork;
183   unsigned long int hash = __nis_hash (grp->gr_name,
184                                        strlen (grp->gr_name)) % modulo;
185
186   if (debug_flag)
187     dbg_log (_("grp_add_cache (%s)"), grp->gr_name);
188
189   work = &grptbl[hash];
190
191   if (grptbl[hash].grp == NULL)
192     grptbl[hash].grp = save_grp (grp);
193   else
194     {
195       while (work->next != NULL)
196         work = work->next;
197
198       work->next = calloc (1, sizeof (grphash));
199       work->next->grp = save_grp (grp);
200       work = work->next;
201     }
202
203   time (&work->create);
204   gidwork = &gidtbl[grp->gr_gid % modulo];
205   if (gidwork->grptr == NULL)
206     gidwork->grptr = work->grp;
207   else
208     {
209       while (gidwork->next != NULL)
210         gidwork = gidwork->next;
211
212       gidwork->next = calloc (1, sizeof (gidhash));
213       gidwork->next->grptr = work->grp;
214     }
215
216   return 0;
217 }
218
219 static struct group *
220 cache_search_name (const char *name)
221 {
222   grphash *work;
223   unsigned long int hash = __nis_hash (name, strlen(name)) % modulo;
224
225   work = &grptbl[hash];
226
227   while (work->grp != NULL)
228     {
229       if (strcmp (work->grp->gr_name, name) == 0)
230         return work->grp;
231       if (work->next != NULL)
232         work = work->next;
233       else
234         return NULL;
235     }
236   return NULL;
237 }
238
239 static struct group *
240 cache_search_gid (gid_t gid)
241 {
242   gidhash *work;
243
244   work = &gidtbl[gid % modulo];
245
246   while (work->grptr != NULL)
247     {
248       if (work->grptr->gr_gid == gid)
249         return work->grptr;
250       if (work->next != NULL)
251         work = work->next;
252       else
253         return NULL;
254     }
255   return NULL;
256 }
257
258 static int
259 add_negcache (char *key)
260 {
261   neghash *work;
262   unsigned long int hash = __nis_hash (key, strlen (key)) % modulo;
263
264   if (debug_flag)
265     dbg_log (_("grp_add_netgache (%s|%ld)"), key, hash);
266
267   work = &negtbl[hash];
268
269   if (negtbl[hash].key == NULL)
270     {
271       negtbl[hash].key = strdup (key);
272       negtbl[hash].next = NULL;
273     }
274   else
275     {
276       while (work->next != NULL)
277         work = work->next;
278
279       work->next = calloc (1, sizeof (neghash));
280       work->next->key = strdup (key);
281       work = work->next;
282     }
283
284   time (&work->create);
285   return 0;
286 }
287
288 static int
289 cache_search_neg (const char *key)
290 {
291   neghash *work;
292   unsigned long int hash = __nis_hash (key, strlen (key)) % modulo;
293
294   if (debug_flag)
295     dbg_log (_("grp_cache_search_neg (%s|%ld)"), key, hash);
296
297   work = &negtbl[hash];
298
299   while (work->key != NULL)
300     {
301       if (strcmp (work->key, key) == 0)
302         return 1;
303       if (work->next != NULL)
304         work = work->next;
305       else
306         return 0;
307     }
308   return 0;
309 }
310
311 void *
312 cache_getgrnam (void *v_param)
313 {
314   param_t *param = (param_t *)v_param;
315   struct group *grp;
316
317   pthread_rwlock_rdlock (&grplock);
318   grp = cache_search_name (param->key);
319
320   /* I don't like it to hold the read only lock longer, but it is
321      necessary to avoid to much malloc/free/strcpy.  */
322
323   if (grp != NULL)
324     {
325       if (debug_flag)
326         dbg_log (_("Found \"%s\" in cache !"), param->key);
327
328       ++poshit;
329       gr_send_answer (param->conn, grp);
330       close_socket (param->conn);
331
332       pthread_rwlock_unlock (&grplock);
333     }
334   else
335     {
336       int status;
337       int buflen = 1024;
338       char *buffer = calloc (1, buflen);
339       struct group resultbuf;
340
341       if (debug_flag)
342         dbg_log (_("Doesn't found \"%s\" in cache !"), param->key);
343
344       pthread_rwlock_unlock (&grplock);
345
346       pthread_rwlock_rdlock (&neglock);
347       status = cache_search_neg (param->key);
348       pthread_rwlock_unlock (&neglock);
349
350       if (status == 0)
351         {
352           while (buffer != NULL
353                  && (getgrnam_r (param->key, &resultbuf, buffer, buflen, &grp)
354                      != 0)
355                  && errno == ERANGE)
356             {
357               errno = 0;
358               buflen += 1024;
359               buffer = realloc (buffer, buflen);
360             }
361
362           if (buffer != NULL && grp != NULL)
363             {
364               struct group *tmp;
365
366               ++poshit;
367               pthread_rwlock_wrlock (&grplock);
368               /* While we are waiting on the lock, somebody else could
369                  add this entry.  */
370               tmp = cache_search_name (param->key);
371               if (tmp == NULL)
372                 add_cache (grp);
373               pthread_rwlock_unlock (&grplock);
374             }
375           else
376             {
377               pthread_rwlock_wrlock (&neglock);
378               add_negcache (param->key);
379               ++negmiss;
380               pthread_rwlock_unlock (&neglock);
381             }
382         }
383       else
384         ++neghit;
385
386       gr_send_answer (param->conn, grp);
387       close_socket (param->conn);
388       if (buffer != NULL)
389         free (buffer);
390     }
391   free (param->key);
392   free (param);
393   return NULL;
394 }
395
396 void *
397 cache_gr_disabled (void *v_param)
398 {
399   param_t *param = (param_t *)v_param;
400
401   if (debug_flag)
402     dbg_log (_("\tgroup cache is disabled\n"));
403
404   gr_send_disabled (param->conn);
405   return NULL;
406 }
407
408 void *
409 cache_getgrgid (void *v_param)
410 {
411   param_t *param = (param_t *)v_param;
412   struct group *grp, resultbuf;
413   gid_t gid = strtol (param->key, NULL, 10);
414
415   pthread_rwlock_rdlock (&grplock);
416   grp = cache_search_gid (gid);
417
418   /* I don't like it to hold the read only lock longer, but it is
419      necessary to avoid to much malloc/free/strcpy.  */
420
421   if (grp != NULL)
422     {
423       if (debug_flag)
424         dbg_log (_("Found \"%d\" in cache !"), gid);
425
426       ++poshit;
427       gr_send_answer (param->conn, grp);
428       close_socket (param->conn);
429
430       pthread_rwlock_unlock (&grplock);
431     }
432   else
433     {
434       int buflen = 1024;
435       char *buffer = malloc (buflen);
436       int status;
437
438       if (debug_flag)
439         dbg_log (_("Doesn't found \"%d\" in cache !"), gid);
440
441       pthread_rwlock_unlock (&grplock);
442
443       pthread_rwlock_rdlock (&neglock);
444       status = cache_search_neg (param->key);
445       pthread_rwlock_unlock (&neglock);
446
447       if (status == 0)
448         {
449           while (buffer != NULL
450                  && (getgrgid_r (gid, &resultbuf, buffer, buflen, &grp) != 0)
451                  && errno == ERANGE)
452             {
453               errno = 0;
454               buflen += 1024;
455               buffer = realloc (buffer, buflen);
456             }
457
458           if (buffer != NULL && grp != NULL)
459             {
460               struct group *tmp;
461
462               ++posmiss;
463               pthread_rwlock_wrlock (&grplock);
464               /* While we are waiting on the lock, somebody else could
465                  add this entry.  */
466               tmp = cache_search_gid (gid);
467               if (tmp == NULL)
468                 add_cache (grp);
469               pthread_rwlock_unlock (&grplock);
470             }
471           else
472             {
473               ++negmiss;
474               pthread_rwlock_wrlock (&neglock);
475               add_negcache (param->key);
476               pthread_rwlock_unlock (&neglock);
477             }
478         }
479       else
480         ++neghit;
481
482       gr_send_answer (param->conn, grp);
483       close_socket (param->conn);
484       if (buffer != NULL)
485         free (buffer);
486     }
487   free (param->key);
488   free (param);
489   return NULL;
490 }
491
492 static void *
493 grptable_update (void *v)
494 {
495   time_t now;
496   int i;
497
498   sleep (20);
499
500   while (!do_shutdown)
501     {
502       if (debug_flag > 2)
503         dbg_log (_("(grptable_update) Wait for write lock!"));
504
505       pthread_rwlock_wrlock (&grplock);
506
507       if (debug_flag > 2)
508         dbg_log (_("(grptable_update) Have write lock"));
509
510       time (&now);
511       for (i = 0; i < modulo; ++i)
512         {
513           grphash *work = &grptbl[i];
514
515           while (work && work->grp)
516             {
517               if ((now - work->create) >= postimeout)
518                 {
519                   gidhash *uh = &gidtbl[work->grp->gr_gid % modulo];
520
521                   if (debug_flag)
522                     dbg_log (_("Give \"%s\" free"), work->grp->gr_name);
523
524                   while (uh && uh->grptr)
525                     {
526                       if (uh->grptr->gr_gid == work->grp->gr_gid)
527                         {
528                           if (debug_flag > 3)
529                             dbg_log (_("Give gid for \"%s\" free"),
530                                      work->grp->gr_name);
531                           if (uh->next != NULL)
532                             {
533                               gidhash *tmp = uh->next;
534                               uh->grptr = tmp->grptr;
535                               uh->next = tmp->next;
536                               free (tmp);
537                             }
538                           else
539                             uh->grptr = NULL;
540                         }
541                       uh = uh->next;
542                     }
543
544                   free_grp (work->grp);
545                   if (work->next != NULL)
546                     {
547                       grphash *tmp = work->next;
548                       work->create = tmp->create;
549                       work->next = tmp->next;
550                       work->grp = tmp->grp;
551                       free (tmp);
552                     }
553                   else
554                     work->grp = NULL;
555                 }
556               work = work->next;
557             }
558         }
559       if (debug_flag > 2)
560         dbg_log (_("(grptable_update) Release wait lock"));
561       pthread_rwlock_unlock (&grplock);
562       sleep (20);
563     }
564   return NULL;
565 }
566
567 static void *
568 negtable_update (void *v)
569 {
570   time_t now;
571   int i;
572
573   sleep (30);
574
575   while (!do_shutdown)
576     {
577       if (debug_flag > 2)
578         dbg_log (_("(neggrptable_update) Wait for write lock!"));
579
580       pthread_rwlock_wrlock (&neglock);
581
582       if (debug_flag > 2)
583         dbg_log (_("(neggrptable_update) Have write lock"));
584
585       time (&now);
586       for (i = 0; i < modulo; ++i)
587         {
588           neghash *work = &negtbl[i];
589
590           while (work && work->key)
591             {
592               if ((now - work->create) >= negtimeout)
593                 {
594                   if (debug_flag)
595                     dbg_log (_("Give \"%s\" free"), work->key);
596
597                   free (work->key);
598
599                   if (work->next != NULL)
600                     {
601                       neghash *tmp = work->next;
602                       work->create = tmp->create;
603                       work->next = tmp->next;
604                       work->key = tmp->key;
605                       free (tmp);
606                     }
607                   else
608                     work->key = NULL;
609                 }
610               work = work->next;
611             }
612         }
613       if (debug_flag > 2)
614         dbg_log (_("(neggrptable_update) Release wait lock"));
615       pthread_rwlock_unlock (&neglock);
616       sleep (10);
617     }
618   return NULL;
619 }