summaryrefslogtreecommitdiffstats
path: root/fuse/dircache.c
diff options
context:
space:
mode:
Diffstat (limited to 'fuse/dircache.c')
-rw-r--r--fuse/dircache.c408
1 files changed, 408 insertions, 0 deletions
diff --git a/fuse/dircache.c b/fuse/dircache.c
new file mode 100644
index 00000000..545b9f38
--- /dev/null
+++ b/fuse/dircache.c
@@ -0,0 +1,408 @@
+/* guestmount - mount guests using libguestfs and FUSE
+ * Copyright (C) 2009 Red Hat Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ * Derived from the example program 'fusexmp.c':
+ * Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+ *
+ * This program can be distributed under the terms of the GNU GPL.
+ * See the file COPYING.
+ */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <assert.h>
+#include <sys/time.h>
+#include <sys/types.h>
+
+#include <guestfs.h>
+
+#include "hash.h"
+#include "hash-pjw.h"
+
+#include "dircache.h"
+
+extern int verbose;
+extern int dir_cache_timeout;
+
+static inline char *
+bad_cast (char const *s)
+{
+ return (char *) s;
+}
+
+/* Note on attribute caching: FUSE can cache filesystem attributes for
+ * short periods of time (configurable via -o attr_timeout). It
+ * doesn't cache xattrs, and in any case FUSE caching doesn't solve
+ * the problem that we have to make a series of guestfs_lstat and
+ * guestfs_lgetxattr calls when we first list a directory (thus, many
+ * round trips).
+ *
+ * For this reason, we also implement a readdir cache here which is
+ * invoked when a readdir call is made. readdir is modified so that
+ * as well as reading the directory, it also requests all the stat
+ * structures, xattrs and readlinks of all entries in the directory,
+ * and these are added to the cache here (for a short, configurable
+ * period of time) in anticipation that they will be needed
+ * immediately afterwards, which is usually the case when the user is
+ * doing an "ls"-like operation.
+ *
+ * You can still use FUSE attribute caching on top of this mechanism
+ * if you like.
+ */
+
+struct lsc_entry { /* lstat cache entry */
+ char *pathname; /* full path to the file */
+ time_t timeout; /* when this entry expires */
+ struct stat statbuf; /* statbuf */
+};
+
+struct xac_entry { /* xattr cache entry */
+ /* NB first two fields must be same as lsc_entry */
+ char *pathname; /* full path to the file */
+ time_t timeout; /* when this entry expires */
+ struct guestfs_xattr_list *xattrs;
+};
+
+struct rlc_entry { /* readlink cache entry */
+ /* NB first two fields must be same as lsc_entry */
+ char *pathname; /* full path to the file */
+ time_t timeout; /* when this entry expires */
+ char *link;
+};
+
+static size_t
+gen_hash (void const *x, size_t table_size)
+{
+ struct lsc_entry const *p = x;
+ return hash_pjw (p->pathname, table_size);
+}
+
+static bool
+gen_compare (void const *x, void const *y)
+{
+ struct lsc_entry const *a = x;
+ struct lsc_entry const *b = y;
+ return strcmp (a->pathname, b->pathname) == 0;
+}
+
+static void
+lsc_free (void *x)
+{
+ if (x) {
+ struct lsc_entry *p = x;
+
+ free (p->pathname);
+ free (p);
+ }
+}
+
+static void
+xac_free (void *x)
+{
+ if (x) {
+ struct xac_entry *p = x;
+
+ guestfs_free_xattr_list (p->xattrs);
+ lsc_free (x);
+ }
+}
+
+static void
+rlc_free (void *x)
+{
+ if (x) {
+ struct rlc_entry *p = x;
+
+ free (p->link);
+ lsc_free (x);
+ }
+}
+
+static Hash_table *lsc_ht, *xac_ht, *rlc_ht;
+
+void
+init_dir_caches (void)
+{
+ lsc_ht = hash_initialize (1024, NULL, gen_hash, gen_compare, lsc_free);
+ xac_ht = hash_initialize (1024, NULL, gen_hash, gen_compare, xac_free);
+ rlc_ht = hash_initialize (1024, NULL, gen_hash, gen_compare, rlc_free);
+ if (!lsc_ht || !xac_ht || !rlc_ht) {
+ fprintf (stderr, "guestmount: could not initialize dir cache hashtables\n");
+ exit (1);
+ }
+}
+
+void
+free_dir_caches (void)
+{
+ hash_free (lsc_ht);
+ hash_free (xac_ht);
+ hash_free (rlc_ht);
+}
+
+struct gen_remove_data {
+ time_t now;
+ Hash_table *ht;
+ Hash_data_freer freer;
+};
+
+static bool
+gen_remove_if_expired (void *x, void *data)
+{
+ /* XXX hash_do_for_each was observed calling this function
+ * with x == NULL.
+ */
+ if (x) {
+ struct lsc_entry *p = x;
+ struct gen_remove_data *d = data;
+
+ if (p->timeout < d->now) {
+ if (verbose)
+ fprintf (stderr, "dir cache: expiring entry %p (%s)\n",
+ p, p->pathname);
+ d->freer (hash_delete (d->ht, x));
+ }
+ }
+
+ return 1;
+}
+
+static void
+gen_remove_all_expired (Hash_table *ht, Hash_data_freer freer, time_t now)
+{
+ struct gen_remove_data data;
+ data.now = now;
+ data.ht = ht;
+ data.freer = freer;
+
+ /* Careful reading of the documentation to hash _seems_ to indicate
+ * that this is safe, _provided_ we use the default thresholds (in
+ * particular, no shrink threshold).
+ */
+ hash_do_for_each (ht, gen_remove_if_expired, &data);
+}
+
+void
+dir_cache_remove_all_expired (time_t now)
+{
+ gen_remove_all_expired (lsc_ht, lsc_free, now);
+ gen_remove_all_expired (xac_ht, xac_free, now);
+ gen_remove_all_expired (rlc_ht, rlc_free, now);
+}
+
+static int
+gen_replace (Hash_table *ht, struct lsc_entry *new_entry, Hash_data_freer freer)
+{
+ struct lsc_entry *old_entry;
+
+ old_entry = hash_delete (ht, new_entry);
+ freer (old_entry);
+
+ if (verbose && old_entry)
+ fprintf (stderr, "dir cache: this entry replaced old entry %p (%s)\n",
+ old_entry, old_entry->pathname);
+
+ old_entry = hash_insert (ht, new_entry);
+ if (old_entry == NULL) {
+ perror ("hash_insert");
+ freer (new_entry);
+ return -1;
+ }
+ assert (old_entry == new_entry);
+
+ return 0;
+}
+
+int
+lsc_insert (const char *path, const char *name, time_t now,
+ struct stat const *statbuf)
+{
+ struct lsc_entry *entry;
+
+ entry = malloc (sizeof *entry);
+ if (entry == NULL) {
+ perror ("malloc");
+ return -1;
+ }
+
+ size_t len = strlen (path) + strlen (name) + 2;
+ entry->pathname = malloc (len);
+ if (entry->pathname == NULL) {
+ perror ("malloc");
+ free (entry);
+ return -1;
+ }
+ if (strcmp (path, "/") == 0)
+ snprintf (entry->pathname, len, "/%s", name);
+ else
+ snprintf (entry->pathname, len, "%s/%s", path, name);
+
+ memcpy (&entry->statbuf, statbuf, sizeof entry->statbuf);
+
+ entry->timeout = now + dir_cache_timeout;
+
+ if (verbose)
+ fprintf (stderr, "dir cache: inserting lstat entry %p (%s)\n",
+ entry, entry->pathname);
+
+ return gen_replace (lsc_ht, entry, lsc_free);
+}
+
+int
+xac_insert (const char *path, const char *name, time_t now,
+ struct guestfs_xattr_list *xattrs)
+{
+ struct xac_entry *entry;
+
+ entry = malloc (sizeof *entry);
+ if (entry == NULL) {
+ perror ("malloc");
+ return -1;
+ }
+
+ size_t len = strlen (path) + strlen (name) + 2;
+ entry->pathname = malloc (len);
+ if (entry->pathname == NULL) {
+ perror ("malloc");
+ free (entry);
+ return -1;
+ }
+ if (strcmp (path, "/") == 0)
+ snprintf (entry->pathname, len, "/%s", name);
+ else
+ snprintf (entry->pathname, len, "%s/%s", path, name);
+
+ entry->xattrs = xattrs;
+
+ entry->timeout = now + dir_cache_timeout;
+
+ if (verbose)
+ fprintf (stderr, "dir cache: inserting xattr entry %p (%s)\n",
+ entry, entry->pathname);
+
+ return gen_replace (xac_ht, (struct lsc_entry *) entry, xac_free);
+}
+
+int
+rlc_insert (const char *path, const char *name, time_t now,
+ char *link)
+{
+ struct rlc_entry *entry;
+
+ entry = malloc (sizeof *entry);
+ if (entry == NULL) {
+ perror ("malloc");
+ return -1;
+ }
+
+ size_t len = strlen (path) + strlen (name) + 2;
+ entry->pathname = malloc (len);
+ if (entry->pathname == NULL) {
+ perror ("malloc");
+ free (entry);
+ return -1;
+ }
+ if (strcmp (path, "/") == 0)
+ snprintf (entry->pathname, len, "/%s", name);
+ else
+ snprintf (entry->pathname, len, "%s/%s", path, name);
+
+ entry->link = link;
+
+ entry->timeout = now + dir_cache_timeout;
+
+ if (verbose)
+ fprintf (stderr, "dir cache: inserting readlink entry %p (%s)\n",
+ entry, entry->pathname);
+
+ return gen_replace (rlc_ht, (struct lsc_entry *) entry, rlc_free);
+}
+
+const struct stat *
+lsc_lookup (const char *pathname)
+{
+ const struct lsc_entry key = { .pathname = bad_cast (pathname) };
+ struct lsc_entry *entry;
+ time_t now;
+
+ time (&now);
+
+ entry = hash_lookup (lsc_ht, &key);
+ if (entry && entry->timeout >= now)
+ return &entry->statbuf;
+ else
+ return NULL;
+}
+
+const struct guestfs_xattr_list *
+xac_lookup (const char *pathname)
+{
+ const struct xac_entry key = { .pathname = bad_cast (pathname) };
+ struct xac_entry *entry;
+ time_t now;
+
+ time (&now);
+
+ entry = hash_lookup (xac_ht, &key);
+ if (entry && entry->timeout >= now)
+ return entry->xattrs;
+ else
+ return NULL;
+}
+
+const char *
+rlc_lookup (const char *pathname)
+{
+ const struct rlc_entry key = { .pathname = bad_cast (pathname) };
+ struct rlc_entry *entry;
+ time_t now;
+
+ time (&now);
+
+ entry = hash_lookup (rlc_ht, &key);
+ if (entry && entry->timeout >= now)
+ return entry->link;
+ else
+ return NULL;
+}
+
+static void
+lsc_remove (Hash_table *ht, const char *pathname, Hash_data_freer freer)
+{
+ const struct lsc_entry key = { .pathname = bad_cast (pathname) };
+ struct lsc_entry *entry;
+
+ entry = hash_delete (ht, &key);
+
+ if (verbose)
+ fprintf (stderr, "dir cache: invalidating entry %p (%s)\n",
+ entry, entry->pathname);
+
+ freer (entry);
+}
+
+void
+dir_cache_invalidate (const char *path)
+{
+ lsc_remove (lsc_ht, path, lsc_free);
+ lsc_remove (xac_ht, path, xac_free);
+ lsc_remove (rlc_ht, path, rlc_free);
+}