summaryrefslogtreecommitdiffstats
path: root/fuse/dircache.c
diff options
context:
space:
mode:
authorRichard Jones <rjones@redhat.com>2009-10-30 16:13:13 +0000
committerRichard Jones <rjones@redhat.com>2009-11-03 15:57:26 +0000
commit429de2254176e470035eef05e0f3e9910d46863c (patch)
treecf278abb7fba6de900b049e289c06df424ea88cc /fuse/dircache.c
parent08c9bf5e22ecf06e36cf128416a62214704da411 (diff)
downloadlibguestfs-429de2254176e470035eef05e0f3e9910d46863c.tar.gz
libguestfs-429de2254176e470035eef05e0f3e9910d46863c.tar.xz
libguestfs-429de2254176e470035eef05e0f3e9910d46863c.zip
FUSE filesystem support.
This implements FUSE filesystem support so that any libguestfs- accessible disk image can be mounted as a local filesystem. Note: file writes (ie. write(2) system call) is not yet implemented. The API needs more test coverage, particularly lesser-used system calls. The big unresolved issue is UID/GID mapping between guest filesystem IDs and the host. It's not easy to automate this because you need extra details about the guest itself in order to get to its UID->username map (eg. /etc/passwd from the guest).
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);
+}