FUSE filesystem support.
[libguestfs.git] / fuse / dircache.c
diff --git a/fuse/dircache.c b/fuse/dircache.c
new file mode 100644 (file)
index 0000000..545b9f3
--- /dev/null
@@ -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);
+}