.
[kopensolaris-gnu/glibc.git] / nscd / cache.c
1 /* Copyright (c) 1998, 1999 Free Software Foundation, Inc.
2    This file is part of the GNU C Library.
3    Contributed by Ulrich Drepper <drepper@cygnus.com>, 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 <atomicity.h>
21 #include <errno.h>
22 #include <error.h>
23 #include <limits.h>
24 #include <stdlib.h>
25 #include <string.h>
26 #include <arpa/inet.h>
27 #include <rpcsvc/nis.h>
28 #include <sys/param.h>
29 #include <sys/stat.h>
30 #include <sys/uio.h>
31
32 #include "nscd.h"
33 #include "dbg_log.h"
34
35 /* Search the cache for a matching entry and return it when found.  If
36    this fails search the negative cache and return (void *) -1 if this
37    search was successful.  Otherwise return NULL.
38
39    This function must be called with the read-lock held.  */
40 struct hashentry *
41 cache_search (int type, void *key, size_t len, struct database *table)
42 {
43   unsigned long int hash = __nis_hash (key, len) % table->module;
44   struct hashentry *work;
45
46   work = table->array[hash];
47
48   while (work != NULL)
49     {
50       if (type == work->type
51           && len == work->len && memcmp (key, work->key, len) == 0)
52         {
53           /* We found the entry.  Increment the appropriate counter.  */
54           if (work->data == (void *) -1)
55             ++table->neghit;
56           else
57             ++table->poshit;
58
59           return work;
60         }
61
62       work = work->next;
63     }
64
65   return NULL;
66 }
67
68 /* Add a new entry to the cache.  The return value is zero if the function
69    call was successful.
70
71    This function must be called with the read-lock held.
72
73    We modify the table but we nevertheless only acquire a read-lock.
74    This is ok since we use operations which would be safe even without
75    locking, given that the `prune_cache' function never runs.  Using
76    the readlock reduces the chance of conflicts.  */
77 void
78 cache_add (int type, void *key, size_t len, const void *packet, size_t total,
79            void *data, int last, time_t t, struct database *table)
80 {
81   unsigned long int hash = __nis_hash (key, len) % table->module;
82   struct hashentry *newp;
83
84   newp = malloc (sizeof (struct hashentry));
85   if (newp == NULL)
86     error (EXIT_FAILURE, errno, _("while allocating hash table entry"));
87
88   newp->type = type;
89   newp->len = len;
90   newp->key = key;
91   newp->data = data;
92   newp->timeout = t;
93   newp->packet = packet;
94   newp->total = total;
95
96   newp->last = last;
97
98   /* Put the new entry in the first position.  */
99   do
100     newp->next = table->array[hash];
101   while (! compare_and_swap ((volatile long int *) &table->array[hash],
102                              (long int) newp->next, (long int) newp));
103
104   /* Update the statistics.  */
105   if (data == (void *) -1)
106     ++table->negmiss;
107   else if (last)
108     ++table->posmiss;
109 }
110
111 /* Walk through the table and remove all entries which lifetime ended.
112
113    We have a problem here.  To actually remove the entries we must get
114    the write-lock.  But since we want to keep the time we have the
115    lock as short as possible we cannot simply acquire the lock when we
116    start looking for timedout entries.
117
118    Therefore we do it in two stages: first we look for entries which
119    must be invalidated and remember them.  Then we get the lock and
120    actually remove them.  This is complicated by the way we have to
121    free the data structures since some hash table entries share the same
122    data.  */
123 void
124 prune_cache (struct database *table, time_t now)
125 {
126   size_t cnt = table->module;
127   int mark[cnt];
128   int anything = 0;
129   size_t first = cnt + 1;
130   size_t last = 0;
131
132   /* If we check for the modification of the underlying file we invalidate
133      the entries also in this case.  */
134   if (table->check_file)
135     {
136       struct stat st;
137
138       if (stat (table->filename, &st) < 0)
139         {
140           char buf[128];
141           /* We cannot stat() the file, disable file checking.  */
142           dbg_log (_("cannot stat() file `%s': %s"),
143                    table->filename, strerror_r (errno, buf, sizeof (buf)));
144           table->check_file = 0;
145         }
146       else
147         {
148           if (st.st_mtime != table->file_mtime)
149             /* The file changed.  Invalidate all entries.  */
150             now = LONG_MAX;
151         }
152     }
153
154   /* We run through the table and find values which are not valid anymore.
155
156    Note that for the initial step, finding the entries to be removed,
157    we don't need to get any lock.  It is at all timed assured that the
158    linked lists are set up correctly and that no second thread prunes
159    the cache.  */
160   do
161     {
162       struct hashentry *runp = table->array[--cnt];
163
164       mark[cnt] = 0;
165
166       while (runp != NULL)
167         {
168           if (runp->timeout < now)
169             {
170               ++mark[cnt];
171               anything = 1;
172               first = MIN (first, cnt);
173               last = MAX (last, cnt);
174             }
175           runp = runp->next;
176         }
177     }
178   while (cnt > 0);
179
180   if (anything)
181     {
182       struct hashentry *head = NULL;
183
184       /* Now we have to get the write lock since we are about to modify
185          the table.  */
186       pthread_rwlock_wrlock (&table->lock);
187
188       while (first <= last)
189         {
190           if (mark[first] > 0)
191             {
192               struct hashentry *runp;
193
194               while (table->array[first]->timeout < now)
195                 {
196                   table->array[first]->dellist = head;
197                   head = table->array[first];
198                   table->array[first] = head->next;
199                   if (--mark[first] == 0)
200                     break;
201                 }
202
203               runp = table->array[first];
204               while (mark[first] > 0)
205                 {
206                   if (runp->next->timeout < now)
207                     {
208                       runp->next->dellist = head;
209                       head = runp->next;
210                       runp->next = head->next;
211                       --mark[first];
212                     }
213                   else
214                     runp = runp->next;
215                 }
216             }
217           ++first;
218         }
219
220       /* It's all done.  */
221       pthread_rwlock_unlock (&table->lock);
222
223       /* And another run to free the data.  */
224       do
225         {
226           struct hashentry *old = head;
227
228           if (debug_level > 0)
229             {
230               char buf[INET6_ADDRSTRLEN];
231               const char *str;
232
233               if ((old->type == GETHOSTBYADDR || old->type == GETHOSTBYADDRv6)
234                   && (old->last || old->data == (void *) -1))
235                 {
236                   inet_ntop (old->type == GETHOSTBYADDR ? AF_INET : AF_INET6,
237                              old->key, buf, sizeof (buf));
238                   str = buf;
239                 }
240               else
241                 str = old->last ? old->key : (old->data == (void *) -1
242                                               ? old->key : "???");
243
244               dbg_log ("remove %s entry \"%s\"", serv2str[old->type], str);
245             }
246
247           /* Free the data structures.  */
248           if (old->data == (void *) -1)
249             free (old->key);
250           else if (old->last)
251             free (old->data);
252
253           head = head->dellist;
254
255           free (old);
256         }
257       while (head != NULL);
258     }
259 }