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