indent with spaces, not TABs
[libguestfs.git] / fuse / dircache.c
1 /* guestmount - mount guests using libguestfs and FUSE
2  * Copyright (C) 2009 Red Hat Inc.
3  *
4  * This program is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation; either version 2 of the License, or
7  * (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program; if not, write to the Free Software
16  * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
17  *
18  * Derived from the example program 'fusexmp.c':
19  * Copyright (C) 2001-2007  Miklos Szeredi <miklos@szeredi.hu>
20  *
21  * This program can be distributed under the terms of the GNU GPL.
22  * See the file COPYING.
23  */
24
25 #include <config.h>
26
27 #include <stdio.h>
28 #include <stdlib.h>
29 #include <string.h>
30 #include <time.h>
31 #include <assert.h>
32 #include <sys/time.h>
33 #include <sys/types.h>
34
35 #include <guestfs.h>
36
37 #include "hash.h"
38 #include "hash-pjw.h"
39
40 #include "dircache.h"
41
42 extern int verbose;
43 extern int dir_cache_timeout;
44
45 static inline char *
46 bad_cast (char const *s)
47 {
48   return (char *) s;
49 }
50
51 /* Note on attribute caching: FUSE can cache filesystem attributes for
52  * short periods of time (configurable via -o attr_timeout).  It
53  * doesn't cache xattrs, and in any case FUSE caching doesn't solve
54  * the problem that we have to make a series of guestfs_lstat and
55  * guestfs_lgetxattr calls when we first list a directory (thus, many
56  * round trips).
57  *
58  * For this reason, we also implement a readdir cache here which is
59  * invoked when a readdir call is made.  readdir is modified so that
60  * as well as reading the directory, it also requests all the stat
61  * structures, xattrs and readlinks of all entries in the directory,
62  * and these are added to the cache here (for a short, configurable
63  * period of time) in anticipation that they will be needed
64  * immediately afterwards, which is usually the case when the user is
65  * doing an "ls"-like operation.
66  *
67  * You can still use FUSE attribute caching on top of this mechanism
68  * if you like.
69  */
70
71 struct lsc_entry {              /* lstat cache entry */
72   char *pathname;               /* full path to the file */
73   time_t timeout;               /* when this entry expires */
74   struct stat statbuf;          /* statbuf */
75 };
76
77 struct xac_entry {              /* xattr cache entry */
78   /* NB first two fields must be same as lsc_entry */
79   char *pathname;               /* full path to the file */
80   time_t timeout;               /* when this entry expires */
81   struct guestfs_xattr_list *xattrs;
82 };
83
84 struct rlc_entry {              /* readlink cache entry */
85   /* NB first two fields must be same as lsc_entry */
86   char *pathname;               /* full path to the file */
87   time_t timeout;               /* when this entry expires */
88   char *link;
89 };
90
91 static size_t
92 gen_hash (void const *x, size_t table_size)
93 {
94   struct lsc_entry const *p = x;
95   return hash_pjw (p->pathname, table_size);
96 }
97
98 static bool
99 gen_compare (void const *x, void const *y)
100 {
101   struct lsc_entry const *a = x;
102   struct lsc_entry const *b = y;
103   return strcmp (a->pathname, b->pathname) == 0;
104 }
105
106 static void
107 lsc_free (void *x)
108 {
109   if (x) {
110     struct lsc_entry *p = x;
111
112     free (p->pathname);
113     free (p);
114   }
115 }
116
117 static void
118 xac_free (void *x)
119 {
120   if (x) {
121     struct xac_entry *p = x;
122
123     guestfs_free_xattr_list (p->xattrs);
124     lsc_free (x);
125   }
126 }
127
128 static void
129 rlc_free (void *x)
130 {
131   if (x) {
132     struct rlc_entry *p = x;
133
134     free (p->link);
135     lsc_free (x);
136   }
137 }
138
139 static Hash_table *lsc_ht, *xac_ht, *rlc_ht;
140
141 void
142 init_dir_caches (void)
143 {
144   lsc_ht = hash_initialize (1024, NULL, gen_hash, gen_compare, lsc_free);
145   xac_ht = hash_initialize (1024, NULL, gen_hash, gen_compare, xac_free);
146   rlc_ht = hash_initialize (1024, NULL, gen_hash, gen_compare, rlc_free);
147   if (!lsc_ht || !xac_ht || !rlc_ht) {
148     fprintf (stderr, "guestmount: could not initialize dir cache hashtables\n");
149     exit (1);
150   }
151 }
152
153 void
154 free_dir_caches (void)
155 {
156   hash_free (lsc_ht);
157   hash_free (xac_ht);
158   hash_free (rlc_ht);
159 }
160
161 struct gen_remove_data {
162   time_t now;
163   Hash_table *ht;
164   Hash_data_freer freer;
165 };
166
167 static bool
168 gen_remove_if_expired (void *x, void *data)
169 {
170   /* XXX hash_do_for_each was observed calling this function
171    * with x == NULL.
172    */
173   if (x) {
174     struct lsc_entry *p = x;
175     struct gen_remove_data *d = data;
176
177     if (p->timeout < d->now) {
178       if (verbose)
179         fprintf (stderr, "dir cache: expiring entry %p (%s)\n",
180                  p, p->pathname);
181       d->freer (hash_delete (d->ht, x));
182     }
183   }
184
185   return 1;
186 }
187
188 static void
189 gen_remove_all_expired (Hash_table *ht, Hash_data_freer freer, time_t now)
190 {
191   struct gen_remove_data data;
192   data.now = now;
193   data.ht = ht;
194   data.freer = freer;
195
196   /* Careful reading of the documentation to hash _seems_ to indicate
197    * that this is safe, _provided_ we use the default thresholds (in
198    * particular, no shrink threshold).
199    */
200   hash_do_for_each (ht, gen_remove_if_expired, &data);
201 }
202
203 void
204 dir_cache_remove_all_expired (time_t now)
205 {
206   gen_remove_all_expired (lsc_ht, lsc_free, now);
207   gen_remove_all_expired (xac_ht, xac_free, now);
208   gen_remove_all_expired (rlc_ht, rlc_free, now);
209 }
210
211 static int
212 gen_replace (Hash_table *ht, struct lsc_entry *new_entry, Hash_data_freer freer)
213 {
214   struct lsc_entry *old_entry;
215
216   old_entry = hash_delete (ht, new_entry);
217   freer (old_entry);
218
219   if (verbose && old_entry)
220     fprintf (stderr, "dir cache: this entry replaced old entry %p (%s)\n",
221              old_entry, old_entry->pathname);
222
223   old_entry = hash_insert (ht, new_entry);
224   if (old_entry == NULL) {
225     perror ("hash_insert");
226     freer (new_entry);
227     return -1;
228   }
229   assert (old_entry == new_entry);
230
231   return 0;
232 }
233
234 int
235 lsc_insert (const char *path, const char *name, time_t now,
236             struct stat const *statbuf)
237 {
238   struct lsc_entry *entry;
239
240   entry = malloc (sizeof *entry);
241   if (entry == NULL) {
242     perror ("malloc");
243     return -1;
244   }
245
246   size_t len = strlen (path) + strlen (name) + 2;
247   entry->pathname = malloc (len);
248   if (entry->pathname == NULL) {
249     perror ("malloc");
250     free (entry);
251     return -1;
252   }
253   if (strcmp (path, "/") == 0)
254     snprintf (entry->pathname, len, "/%s", name);
255   else
256     snprintf (entry->pathname, len, "%s/%s", path, name);
257
258   memcpy (&entry->statbuf, statbuf, sizeof entry->statbuf);
259
260   entry->timeout = now + dir_cache_timeout;
261
262   if (verbose)
263     fprintf (stderr, "dir cache: inserting lstat entry %p (%s)\n",
264              entry, entry->pathname);
265
266   return gen_replace (lsc_ht, entry, lsc_free);
267 }
268
269 int
270 xac_insert (const char *path, const char *name, time_t now,
271             struct guestfs_xattr_list *xattrs)
272 {
273   struct xac_entry *entry;
274
275   entry = malloc (sizeof *entry);
276   if (entry == NULL) {
277     perror ("malloc");
278     return -1;
279   }
280
281   size_t len = strlen (path) + strlen (name) + 2;
282   entry->pathname = malloc (len);
283   if (entry->pathname == NULL) {
284     perror ("malloc");
285     free (entry);
286     return -1;
287   }
288   if (strcmp (path, "/") == 0)
289     snprintf (entry->pathname, len, "/%s", name);
290   else
291     snprintf (entry->pathname, len, "%s/%s", path, name);
292
293   entry->xattrs = xattrs;
294
295   entry->timeout = now + dir_cache_timeout;
296
297   if (verbose)
298     fprintf (stderr, "dir cache: inserting xattr entry %p (%s)\n",
299              entry, entry->pathname);
300
301   return gen_replace (xac_ht, (struct lsc_entry *) entry, xac_free);
302 }
303
304 int
305 rlc_insert (const char *path, const char *name, time_t now,
306             char *link)
307 {
308   struct rlc_entry *entry;
309
310   entry = malloc (sizeof *entry);
311   if (entry == NULL) {
312     perror ("malloc");
313     return -1;
314   }
315
316   size_t len = strlen (path) + strlen (name) + 2;
317   entry->pathname = malloc (len);
318   if (entry->pathname == NULL) {
319     perror ("malloc");
320     free (entry);
321     return -1;
322   }
323   if (strcmp (path, "/") == 0)
324     snprintf (entry->pathname, len, "/%s", name);
325   else
326     snprintf (entry->pathname, len, "%s/%s", path, name);
327
328   entry->link = link;
329
330   entry->timeout = now + dir_cache_timeout;
331
332   if (verbose)
333     fprintf (stderr, "dir cache: inserting readlink entry %p (%s)\n",
334              entry, entry->pathname);
335
336   return gen_replace (rlc_ht, (struct lsc_entry *) entry, rlc_free);
337 }
338
339 const struct stat *
340 lsc_lookup (const char *pathname)
341 {
342   const struct lsc_entry key = { .pathname = bad_cast (pathname) };
343   struct lsc_entry *entry;
344   time_t now;
345
346   time (&now);
347
348   entry = hash_lookup (lsc_ht, &key);
349   if (entry && entry->timeout >= now)
350     return &entry->statbuf;
351   else
352     return NULL;
353 }
354
355 const struct guestfs_xattr_list *
356 xac_lookup (const char *pathname)
357 {
358   const struct xac_entry key = { .pathname = bad_cast (pathname) };
359   struct xac_entry *entry;
360   time_t now;
361
362   time (&now);
363
364   entry = hash_lookup (xac_ht, &key);
365   if (entry && entry->timeout >= now)
366     return entry->xattrs;
367   else
368     return NULL;
369 }
370
371 const char *
372 rlc_lookup (const char *pathname)
373 {
374   const struct rlc_entry key = { .pathname = bad_cast (pathname) };
375   struct rlc_entry *entry;
376   time_t now;
377
378   time (&now);
379
380   entry = hash_lookup (rlc_ht, &key);
381   if (entry && entry->timeout >= now)
382     return entry->link;
383   else
384     return NULL;
385 }
386
387 static void
388 lsc_remove (Hash_table *ht, const char *pathname, Hash_data_freer freer)
389 {
390   const struct lsc_entry key = { .pathname = bad_cast (pathname) };
391   struct lsc_entry *entry;
392
393   entry = hash_delete (ht, &key);
394
395   if (verbose)
396     fprintf (stderr, "dir cache: invalidating entry %p (%s)\n",
397              entry, entry->pathname);
398
399   freer (entry);
400 }
401
402 void
403 dir_cache_invalidate (const char *path)
404 {
405   lsc_remove (lsc_ht, path, lsc_free);
406   lsc_remove (xac_ht, path, xac_free);
407   lsc_remove (rlc_ht, path, rlc_free);
408 }