/* 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 * * This program can be distributed under the terms of the GNU GPL. * See the file COPYING. */ #include #include #include #include #include #include #include #include #include #include "hash.h" #include "hash-pjw.h" #include "guestmount.h" #include "dircache.h" /* 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 STREQ (a->pathname, b->pathname); } 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 (EXIT_FAILURE); } } 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 (STREQ (path, "/")) 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 (STREQ (path, "/")) 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 (STREQ (path, "/")) 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); }