From: "Richard W.M. Jones" <rjones(a)redhat.com>
Add FUSE support directly to the API. Instead of needing to use the
external 'guestmount' command, you can mount the libguestfs filesystem
space on a local mountpoint using an API call from any language.
---
configure.ac | 11 +-
fuse/Makefile.am | 2 -
fuse/dircache.c | 400 -----------
fuse/dircache.h | 46 --
fuse/guestmount.c | 965 ++-----------------------
fuse/guestmount.h | 2 -
generator/generator_actions.ml | 59 ++
gobject/Makefile.inc | 4 +
po/POTFILES.in | 39 +-
src/Makefile.am | 9 +-
src/fuse.c | 1523 ++++++++++++++++++++++++++++++++++++++++
src/guestfs-internal.h | 14 +
src/guestfs.pod | 52 ++
13 files changed, 1775 insertions(+), 1351 deletions(-)
delete mode 100644 fuse/dircache.c
delete mode 100644 fuse/dircache.h
create mode 100644 src/fuse.c
diff --git a/configure.ac b/configure.ac
index a16e9a6..90d14b9 100644
--- a/configure.ac
+++ b/configure.ac
@@ -696,9 +696,14 @@ AC_ARG_ENABLE([fuse],
[],
[enable_fuse=yes])
AS_IF([test "x$enable_fuse" != "xno"],
- [PKG_CHECK_MODULES([FUSE],[fuse],,[
- enable_fuse=no
- AC_MSG_WARN([FUSE library and headers are missing, so optional FUSE
module won't be built])])])
+ [PKG_CHECK_MODULES([FUSE],[fuse],
+ [AC_SUBST([FUSE_CFLAGS])
+ AC_SUBST([FUSE_LIBS])
+ AC_DEFINE([HAVE_FUSE],[1],[Define to 1 if you have FUSE])],
+ [enable_fuse=no
+ AC_MSG_WARN([FUSE library and headers are missing, so optional FUSE module
won't be built])
+ ])
+ ])
AM_CONDITIONAL([HAVE_FUSE],[test "x$enable_fuse" != "xno"])
dnl Check for C++ (optional, we just use this to test the header works).
diff --git a/fuse/Makefile.am b/fuse/Makefile.am
index 19c498b..f9d513b 100644
--- a/fuse/Makefile.am
+++ b/fuse/Makefile.am
@@ -37,8 +37,6 @@ SHARED_SOURCE_FILES = \
guestmount_SOURCES = \
$(SHARED_SOURCE_FILES) \
- dircache.c \
- dircache.h \
guestmount.c \
guestmount.h
diff --git a/fuse/dircache.c b/fuse/dircache.c
deleted file mode 100644
index 771e313..0000000
--- a/fuse/dircache.c
+++ /dev/null
@@ -1,400 +0,0 @@
-/* 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Derived from the example program 'fusexmp.c':
- * Copyright (C) 2001-2007 Miklos Szeredi <miklos(a)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 "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 && entry)
- 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);
-}
diff --git a/fuse/dircache.h b/fuse/dircache.h
deleted file mode 100644
index 76ee930..0000000
--- a/fuse/dircache.h
+++ /dev/null
@@ -1,46 +0,0 @@
-/* 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Derived from the example program 'fusexmp.c':
- * Copyright (C) 2001-2007 Miklos Szeredi <miklos(a)szeredi.hu>
- *
- * This program can be distributed under the terms of the GNU GPL.
- * See the file COPYING.
- */
-
-#ifndef GUESTMOUNT_DIRCACHE_H
-#define GUESTMOUNT_DIRCACHE_H 1
-
-#include <time.h>
-#include <sys/stat.h>
-#include <guestfs.h>
-
-extern void init_dir_caches (void);
-extern void free_dir_caches (void);
-extern void dir_cache_remove_all_expired (time_t now);
-extern void dir_cache_invalidate (const char *path);
-
-extern int lsc_insert (const char *path, const char *name, time_t now, struct stat const
*statbuf);
-extern int xac_insert (const char *path, const char *name, time_t now, struct
guestfs_xattr_list *xattrs);
-extern int rlc_insert (const char *path, const char *name, time_t now, char *link);
-extern const struct stat *lsc_lookup (const char *pathname);
-extern const struct guestfs_xattr_list *xac_lookup (const char *pathname);
-extern const char *rlc_lookup (const char *pathname);
-
-extern int dir_cache_timeout;
-
-#endif /* GUESTMOUNT_DIRCACHE_H */
diff --git a/fuse/guestmount.c b/fuse/guestmount.c
index f6c3463..7c5e0af 100644
--- a/fuse/guestmount.c
+++ b/fuse/guestmount.c
@@ -14,12 +14,6 @@
* 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Derived from the example program 'fusexmp.c':
- * Copyright (C) 2001-2007 Miklos Szeredi <miklos(a)szeredi.hu>
- *
- * This program can be distributed under the terms of the GNU GPL.
- * See the file COPYING.
*/
#define FUSE_USE_VERSION 26
@@ -33,30 +27,18 @@
#include <string.h>
#include <unistd.h>
#include <getopt.h>
-#include <fcntl.h>
-#include <dirent.h>
-#include <errno.h>
#include <signal.h>
-#include <time.h>
-#include <assert.h>
-#include <sys/time.h>
-#include <sys/types.h>
-#include <locale.h>
-#include <libintl.h>
+/* We're still using some of FUSE to handle command line options. */
#include <fuse.h>
-#include <guestfs.h>
+
+#include "guestfs.h"
#include "progname.h"
+#include "ignore-value.h"
#include "guestmount.h"
#include "options.h"
-#include "dircache.h"
-
-/* See <attr/xattr.h> */
-#ifndef ENOATTR
-#define ENOATTR ENODATA
-#endif
guestfs_h *g = NULL;
int read_only = 0;
@@ -66,838 +48,13 @@ int inspector = 0;
int keys_from_stdin = 0;
int echo_keys = 0;
const char *libvirt_uri;
-int dir_cache_timeout = 60;
-static int trace_calls = 0;
-
-#define TRACE_CALL(fs,...) \
- if (trace_calls) { \
- fprintf (stderr, "%s: %s (" fs ")\n",
\
- program_name, __func__, __VA_ARGS__); \
- }
-
-static int
-error (void)
-{
- return -guestfs_last_errno (g);
-}
-
-static struct guestfs_xattr_list *
-copy_xattr_list (const struct guestfs_xattr *first, size_t num)
-{
- struct guestfs_xattr_list *xattrs;
-
- xattrs = malloc (sizeof *xattrs);
- if (xattrs == NULL) {
- perror ("malloc");
- return NULL;
- }
-
- xattrs->len = num;
- xattrs->val = malloc (num * sizeof (struct guestfs_xattr));
- if (xattrs->val == NULL) {
- perror ("malloc");
- free (xattrs);
- return NULL;
- }
-
- size_t i;
- for (i = 0; i < num; ++i) {
- xattrs->val[i].attrname = strdup (first[i].attrname);
- xattrs->val[i].attrval_len = first[i].attrval_len;
- xattrs->val[i].attrval = malloc (first[i].attrval_len);
- memcpy (xattrs->val[i].attrval, first[i].attrval, first[i].attrval_len);
- }
-
- return xattrs;
-}
-
-static int
-fg_readdir (const char *path, void *buf, fuse_fill_dir_t filler,
- off_t offset, struct fuse_file_info *fi)
-{
- TRACE_CALL ("%s, %p, %ld", path, buf, (long) offset);
-
- time_t now;
- time (&now);
-
- dir_cache_remove_all_expired (now);
-
- struct guestfs_dirent_list *ents;
-
- ents = guestfs_readdir (g, path);
- if (ents == NULL)
- return error ();
-
- size_t i;
- for (i = 0; i < ents->len; ++i) {
- struct stat stat;
- memset (&stat, 0, sizeof stat);
-
- stat.st_ino = ents->val[i].ino;
- switch (ents->val[i].ftyp) {
- case 'b': stat.st_mode = S_IFBLK; break;
- case 'c': stat.st_mode = S_IFCHR; break;
- case 'd': stat.st_mode = S_IFDIR; break;
- case 'f': stat.st_mode = S_IFIFO; break;
- case 'l': stat.st_mode = S_IFLNK; break;
- case 'r': stat.st_mode = S_IFREG; break;
- case 's': stat.st_mode = S_IFSOCK; break;
- case 'u':
- case '?':
- default: stat.st_mode = 0;
- }
-
- /* Copied from the example, which also ignores 'offset'. I'm
- * not quite sure how this is ever supposed to work on large
- * directories. XXX
- */
- if (filler (buf, ents->val[i].name, &stat, 0))
- break;
- }
-
- /* Now prepopulate the directory caches. This step is just an
- * optimization, don't worry if it fails.
- */
- char **names = malloc ((ents->len + 1) * sizeof (char *));
- if (names) {
- for (i = 0; i < ents->len; ++i)
- names[i] = ents->val[i].name;
- names[i] = NULL;
-
- struct guestfs_stat_list *ss = guestfs_lstatlist (g, path, names);
- if (ss) {
- for (i = 0; i < ss->len; ++i) {
- if (ss->val[i].ino >= 0) {
- struct stat statbuf;
-
- statbuf.st_dev = ss->val[i].dev;
- statbuf.st_ino = ss->val[i].ino;
- statbuf.st_mode = ss->val[i].mode;
- statbuf.st_nlink = ss->val[i].nlink;
- statbuf.st_uid = ss->val[i].uid;
- statbuf.st_gid = ss->val[i].gid;
- statbuf.st_rdev = ss->val[i].rdev;
- statbuf.st_size = ss->val[i].size;
- statbuf.st_blksize = ss->val[i].blksize;
- statbuf.st_blocks = ss->val[i].blocks;
- statbuf.st_atime = ss->val[i].atime;
- statbuf.st_mtime = ss->val[i].mtime;
- statbuf.st_ctime = ss->val[i].ctime;
-
- lsc_insert (path, names[i], now, &statbuf);
- }
- }
- guestfs_free_stat_list (ss);
- }
-
- struct guestfs_xattr_list *xattrs = guestfs_lxattrlist (g, path, names);
- if (xattrs) {
- size_t ni, num;
- struct guestfs_xattr *first;
- struct guestfs_xattr_list *copy;
- for (i = 0, ni = 0; i < xattrs->len; ++i, ++ni) {
- assert (strlen (xattrs->val[i].attrname) == 0);
- if (xattrs->val[i].attrval_len > 0) {
- ++i;
- first = &xattrs->val[i];
- num = 0;
- for (; i < xattrs->len && strlen (xattrs->val[i].attrname)
> 0; ++i)
- num++;
-
- copy = copy_xattr_list (first, num);
- if (copy)
- xac_insert (path, names[ni], now, copy);
-
- i--;
- }
- }
- guestfs_free_xattr_list (xattrs);
- }
-
- char **links = guestfs_readlinklist (g, path, names);
- if (links) {
- for (i = 0; names[i] != NULL; ++i) {
- if (links[i][0])
- /* Note that rlc_insert owns the string links[i] after this, */
- rlc_insert (path, names[i], now, links[i]);
- else
- /* which is why we have to free links[i] here. */
- free (links[i]);
- }
- free (links); /* free the array, not the strings */
- }
-
- free (names);
- }
-
- guestfs_free_dirent_list (ents);
-
- return 0;
-}
-
-static int
-fg_getattr (const char *path, struct stat *statbuf)
-{
- TRACE_CALL ("%s, %p", path, statbuf);
-
- const struct stat *buf;
-
- buf = lsc_lookup (path);
- if (buf) {
- memcpy (statbuf, buf, sizeof *statbuf);
- return 0;
- }
-
- struct guestfs_stat *r;
-
- r = guestfs_lstat (g, path);
- if (r == NULL)
- return error ();
-
- statbuf->st_dev = r->dev;
- statbuf->st_ino = r->ino;
- statbuf->st_mode = r->mode;
- statbuf->st_nlink = r->nlink;
- statbuf->st_uid = r->uid;
- statbuf->st_gid = r->gid;
- statbuf->st_rdev = r->rdev;
- statbuf->st_size = r->size;
- statbuf->st_blksize = r->blksize;
- statbuf->st_blocks = r->blocks;
- statbuf->st_atime = r->atime;
- statbuf->st_mtime = r->mtime;
- statbuf->st_ctime = r->ctime;
-
- guestfs_free_stat (r);
-
- return 0;
-}
-
-/* Nautilus loves to use access(2) to test everything about a file,
- * such as whether it's executable. Therefore treat this a lot like
- * fg_getattr.
- */
-static int
-fg_access (const char *path, int mask)
-{
- TRACE_CALL ("%s, %d", path, mask);
-
- struct stat statbuf;
- int r;
-
- if (read_only && (mask & W_OK))
- return -EROFS;
-
- r = fg_getattr (path, &statbuf);
- if (r < 0 || mask == F_OK)
- return r;
-
- struct fuse_context *fuse = fuse_get_context ();
- int ok = 1;
-
- if (mask & R_OK)
- ok = ok &&
- ( fuse->uid == statbuf.st_uid ? statbuf.st_mode & S_IRUSR
- : fuse->gid == statbuf.st_gid ? statbuf.st_mode & S_IRGRP
- : statbuf.st_mode & S_IROTH);
- if (mask & W_OK)
- ok = ok &&
- ( fuse->uid == statbuf.st_uid ? statbuf.st_mode & S_IWUSR
- : fuse->gid == statbuf.st_gid ? statbuf.st_mode & S_IWGRP
- : statbuf.st_mode & S_IWOTH);
- if (mask & X_OK)
- ok = ok &&
- ( fuse->uid == statbuf.st_uid ? statbuf.st_mode & S_IXUSR
- : fuse->gid == statbuf.st_gid ? statbuf.st_mode & S_IXGRP
- : statbuf.st_mode & S_IXOTH);
-
- return ok ? 0 : -EACCES;
-}
-
-static int
-fg_readlink (const char *path, char *buf, size_t size)
-{
- TRACE_CALL ("%s, %p, %zu", path, buf, size);
-
- const char *r;
- int free_it = 0;
-
- r = rlc_lookup (path);
- if (!r) {
- r = guestfs_readlink (g, path);
- if (r == NULL)
- return error ();
- free_it = 1;
- }
-
- /* Note this is different from the real readlink(2) syscall. FUSE wants
- * the string to be always nul-terminated, even if truncated.
- */
- size_t len = strlen (r);
- if (len > size - 1)
- len = size - 1;
-
- memcpy (buf, r, len);
- buf[len] = '\0';
-
- if (free_it) {
- char *tmp = (char *) r;
- free (tmp);
- }
-
- return 0;
-}
-
-static int
-fg_mknod (const char *path, mode_t mode, dev_t rdev)
-{
- TRACE_CALL ("%s, 0%o, 0x%lx", path, mode, (long) rdev);
-
- int r;
-
- if (read_only) return -EROFS;
-
- dir_cache_invalidate (path);
-
- r = guestfs_mknod (g, mode, major (rdev), minor (rdev), path);
- if (r == -1)
- return error ();
-
- return 0;
-}
-
-static int
-fg_mkdir (const char *path, mode_t mode)
-{
- TRACE_CALL ("%s, 0%o", path, mode);
-
- int r;
-
- if (read_only) return -EROFS;
-
- dir_cache_invalidate (path);
-
- r = guestfs_mkdir_mode (g, path, mode);
- if (r == -1)
- return error ();
-
- return 0;
-}
-
-static int
-fg_unlink (const char *path)
-{
- TRACE_CALL ("%s", path);
-
- int r;
-
- if (read_only) return -EROFS;
-
- dir_cache_invalidate (path);
-
- r = guestfs_rm (g, path);
- if (r == -1)
- return error ();
-
- return 0;
-}
-
-static int
-fg_rmdir (const char *path)
-{
- TRACE_CALL ("%s", path);
-
- int r;
-
- if (read_only) return -EROFS;
-
- dir_cache_invalidate (path);
-
- r = guestfs_rmdir (g, path);
- if (r == -1)
- return error ();
-
- return 0;
-}
-
-static int
-fg_symlink (const char *from, const char *to)
-{
- TRACE_CALL ("%s, %s", from, to);
-
- int r;
-
- if (read_only) return -EROFS;
-
- dir_cache_invalidate (to);
-
- r = guestfs_ln_s (g, from, to);
- if (r == -1)
- return error ();
-
- return 0;
-}
-
-static int
-fg_rename (const char *from, const char *to)
-{
- TRACE_CALL ("%s, %s", from, to);
-
- int r;
-
- if (read_only) return -EROFS;
-
- dir_cache_invalidate (from);
- dir_cache_invalidate (to);
-
- /* XXX It's not clear how close the 'mv' command is to the
- * rename syscall. We might need to add the rename syscall
- * to the guestfs(3) API.
- */
- r = guestfs_mv (g, from, to);
- if (r == -1)
- return error ();
-
- return 0;
-}
-
-static int
-fg_link (const char *from, const char *to)
-{
- TRACE_CALL ("%s, %s", from, to);
-
- int r;
-
- if (read_only) return -EROFS;
-
- dir_cache_invalidate (from);
- dir_cache_invalidate (to);
-
- r = guestfs_ln (g, from, to);
- if (r == -1)
- return error ();
-
- return 0;
-}
-
-static int
-fg_chmod (const char *path, mode_t mode)
-{
- TRACE_CALL ("%s, 0%o", path, mode);
-
- int r;
-
- if (read_only) return -EROFS;
-
- dir_cache_invalidate (path);
-
- r = guestfs_chmod (g, mode, path);
- if (r == -1)
- return error ();
-
- return 0;
-}
-
-static int
-fg_chown (const char *path, uid_t uid, gid_t gid)
-{
- TRACE_CALL ("%s, %ld, %ld", path, (long) uid, (long) gid);
-
- int r;
-
- if (read_only) return -EROFS;
-
- dir_cache_invalidate (path);
-
- r = guestfs_lchown (g, uid, gid, path);
- if (r == -1)
- return error ();
-
- return 0;
-}
-
-static int
-fg_truncate (const char *path, off_t size)
-{
- TRACE_CALL ("%s, %ld", path, (long) size);
-
- int r;
-
- if (read_only) return -EROFS;
-
- dir_cache_invalidate (path);
-
- r = guestfs_truncate_size (g, path, size);
- if (r == -1)
- return error ();
-
- return 0;
-}
-
-static int
-fg_utimens (const char *path, const struct timespec ts[2])
-{
- TRACE_CALL ("%s, [{ %ld, %ld }, { %ld, %ld }]",
- path, ts[0].tv_sec, ts[0].tv_nsec, ts[1].tv_sec, ts[1].tv_nsec);
-
- int r;
-
- if (read_only) return -EROFS;
-
- dir_cache_invalidate (path);
-
- time_t atsecs = ts[0].tv_sec;
- long atnsecs = ts[0].tv_nsec;
- time_t mtsecs = ts[1].tv_sec;
- long mtnsecs = ts[1].tv_nsec;
-
-#ifdef UTIME_NOW
- if (atnsecs == UTIME_NOW)
- atnsecs = -1;
-#endif
-#ifdef UTIME_OMIT
- if (atnsecs == UTIME_OMIT)
- atnsecs = -2;
-#endif
-#ifdef UTIME_NOW
- if (mtnsecs == UTIME_NOW)
- mtnsecs = -1;
-#endif
-#ifdef UTIME_OMIT
- if (mtnsecs == UTIME_OMIT)
- mtnsecs = -2;
-#endif
-
- r = guestfs_utimens (g, path, atsecs, atnsecs, mtsecs, mtnsecs);
- if (r == -1)
- return error ();
-
- return 0;
-}
-
-/* All this function needs to do is to check that the requested open
- * flags are valid. See the notes in <fuse/fuse.h>.
- */
-static int
-fg_open (const char *path, struct fuse_file_info *fi)
-{
- TRACE_CALL ("%s, 0%o", path, fi->flags);
-
- int flags = fi->flags & O_ACCMODE;
-
- if (read_only && flags != O_RDONLY)
- return -EROFS;
-
- return 0;
-}
-
-static int
-fg_read (const char *path, char *buf, size_t size, off_t offset,
- struct fuse_file_info *fi)
-{
- TRACE_CALL ("%s, %p, %zu, %ld", path, buf, size, (long) offset);
-
- char *r;
- size_t rsize;
-
- if (verbose)
- fprintf (stderr, "fg_read: %s: size %zu offset %ju\n",
- path, size, offset);
-
- /* The guestfs protocol limits size to somewhere over 2MB. We just
- * reduce the requested size here accordingly and push the problem
- * up to every user.
http://www.jwz.org/doc/worse-is-better.html
- */
- const size_t limit = 2 * 1024 * 1024;
- if (size > limit)
- size = limit;
-
- r = guestfs_pread (g, path, size, offset, &rsize);
- if (r == NULL)
- return error ();
-
- /* This should never happen, but at least it stops us overflowing
- * the output buffer if it does happen.
- */
- if (rsize > size)
- rsize = size;
-
- memcpy (buf, r, rsize);
- free (r);
-
- return rsize;
-}
-
-static int
-fg_write (const char *path, const char *buf, size_t size,
- off_t offset, struct fuse_file_info *fi)
-{
- TRACE_CALL ("%s, %p, %zu, %ld", path, buf, size, (long) offset);
-
- if (read_only) return -EROFS;
-
- dir_cache_invalidate (path);
-
- /* See fg_read. */
- const size_t limit = 2 * 1024 * 1024;
- if (size > limit)
- size = limit;
-
- int r;
- r = guestfs_pwrite (g, path, buf, size, offset);
- if (r == -1)
- return error ();
-
- return r;
-}
-
-static int
-fg_statfs (const char *path, struct statvfs *stbuf)
-{
- TRACE_CALL ("%s, %p", path, stbuf);
-
- struct guestfs_statvfs *r;
-
- r = guestfs_statvfs (g, path);
- if (r == NULL)
- return error ();
-
- stbuf->f_bsize = r->bsize;
- stbuf->f_frsize = r->frsize;
- stbuf->f_blocks = r->blocks;
- stbuf->f_bfree = r->bfree;
- stbuf->f_bavail = r->bavail;
- stbuf->f_files = r->files;
- stbuf->f_ffree = r->ffree;
- stbuf->f_favail = r->favail;
- stbuf->f_fsid = r->fsid;
- stbuf->f_flag = r->flag;
- stbuf->f_namemax = r->namemax;
-
- guestfs_free_statvfs (r);
-
- return 0;
-}
-
-static int
-fg_release (const char *path, struct fuse_file_info *fi)
-{
- TRACE_CALL ("%s", path);
-
- /* Just a stub. This method is optional and can safely be left
- * unimplemented.
- */
- return 0;
-}
-
-/* Emulate this by calling sync. */
-static int fg_fsync(const char *path, int isdatasync,
- struct fuse_file_info *fi)
-{
- TRACE_CALL ("%s, %d", path, isdatasync);
-
- int r;
-
- r = guestfs_sync (g);
- if (r == -1)
- return error ();
-
- return 0;
-}
-
-static int
-fg_setxattr (const char *path, const char *name, const char *value,
- size_t size, int flags)
-{
- TRACE_CALL ("%s, %s, %p, %zu", path, name, value, size);
-
- int r;
-
- if (read_only) return -EROFS;
-
- dir_cache_invalidate (path);
-
- /* XXX Underlying guestfs(3) API doesn't understand the flags. */
- r = guestfs_lsetxattr (g, name, value, size, path);
- if (r == -1)
- return error ();
-
- return 0;
-}
-
-/* The guestfs(3) API for getting xattrs is much easier to use
- * than the real syscall. Unfortunately we now have to emulate
- * the real syscall using that API :-(
- */
-static int
-fg_getxattr (const char *path, const char *name, char *value,
- size_t size)
-{
- TRACE_CALL ("%s, %s, %p, %zu", path, name, value, size);
-
- const struct guestfs_xattr_list *xattrs;
- int free_attrs = 0;
-
- xattrs = xac_lookup (path);
- if (xattrs == NULL) {
- xattrs = guestfs_lgetxattrs (g, path);
- if (xattrs == NULL)
- return error ();
- free_attrs = 1;
- }
-
- /* Find the matching attribute (index in 'i'). */
- ssize_t r;
- size_t i;
- for (i = 0; i < xattrs->len; ++i) {
- if (STREQ (xattrs->val[i].attrname, name))
- break;
- }
-
- if (i == xattrs->len) { /* not found */
- r = -ENOATTR;
- goto out;
- }
-
- /* The getxattr man page is unclear, but if value == NULL then we
- * return the space required (the caller then makes a second syscall
- * after allocating the required amount of space). If value != NULL
- * then it's not clear what we should do, but it appears we should
- * copy as much as possible and return -ERANGE if there's not enough
- * space in the buffer.
- */
- size_t sz = xattrs->val[i].attrval_len;
- if (value == NULL) {
- r = sz;
- goto out;
- }
-
- if (sz <= size)
- r = sz;
- else {
- r = -ERANGE;
- sz = size;
- }
- memcpy (value, xattrs->val[i].attrval, sz);
-
-out:
- if (free_attrs)
- guestfs_free_xattr_list ((struct guestfs_xattr_list *) xattrs);
-
- return r;
-}
-
-/* Ditto as above. */
-static int
-fg_listxattr (const char *path, char *list, size_t size)
-{
- TRACE_CALL ("%s, %p, %zu", path, list, size);
-
- const struct guestfs_xattr_list *xattrs;
- int free_attrs = 0;
-
- xattrs = xac_lookup (path);
- if (xattrs == NULL) {
- xattrs = guestfs_lgetxattrs (g, path);
- if (xattrs == NULL)
- return error ();
- free_attrs = 1;
- }
-
- /* Calculate how much space is required to hold the result. */
- size_t space = 0;
- size_t len;
- size_t i;
- for (i = 0; i < xattrs->len; ++i) {
- len = strlen (xattrs->val[i].attrname) + 1;
- space += len;
- }
-
- /* The listxattr man page is unclear, but if list == NULL then we
- * return the space required (the caller then makes a second syscall
- * after allocating the required amount of space). If list != NULL
- * then it's not clear what we should do, but it appears we should
- * copy as much as possible and return -ERANGE if there's not enough
- * space in the buffer.
- */
- ssize_t r;
- if (list == NULL) {
- r = space;
- goto out;
- }
-
- r = 0;
- for (i = 0; i < xattrs->len; ++i) {
- len = strlen (xattrs->val[i].attrname) + 1;
- if (size >= len) {
- memcpy (list, xattrs->val[i].attrname, len);
- size -= len;
- list += len;
- r += len;
- } else {
- r = -ERANGE;
- break;
- }
- }
-
- out:
- if (free_attrs)
- guestfs_free_xattr_list ((struct guestfs_xattr_list *) xattrs);
-
- return r;
-}
-
-static int
-fg_removexattr(const char *path, const char *name)
-{
- TRACE_CALL ("%s, %s", path, name);
-
- int r;
-
- if (read_only) return -EROFS;
-
- dir_cache_invalidate (path);
-
- r = guestfs_lremovexattr (g, name, path);
- if (r == -1)
- return error ();
-
- return 0;
-}
-
-static struct fuse_operations fg_operations = {
- .getattr = fg_getattr,
- .access = fg_access,
- .readlink = fg_readlink,
- .readdir = fg_readdir,
- .mknod = fg_mknod,
- .mkdir = fg_mkdir,
- .symlink = fg_symlink,
- .unlink = fg_unlink,
- .rmdir = fg_rmdir,
- .rename = fg_rename,
- .link = fg_link,
- .chmod = fg_chmod,
- .chown = fg_chown,
- .truncate = fg_truncate,
- .utimens = fg_utimens,
- .open = fg_open,
- .read = fg_read,
- .write = fg_write,
- .statfs = fg_statfs,
- .release = fg_release,
- .fsync = fg_fsync,
- .setxattr = fg_setxattr,
- .getxattr = fg_getxattr,
- .listxattr = fg_listxattr,
- .removexattr = fg_removexattr,
-};
static void __attribute__((noreturn))
fuse_help (void)
{
+ static struct fuse_operations null_operations;
const char *tmp_argv[] = { program_name, "--help", NULL };
- fuse_main (2, (char **) tmp_argv, &fg_operations, NULL);
+ fuse_main (2, (char **) tmp_argv, &null_operations, NULL);
exit (EXIT_SUCCESS);
}
@@ -990,20 +147,12 @@ main (int argc, char *argv[])
int option_index;
struct sigaction sa;
- size_t fuse_argc = 0;
- const char **fuse_argv = NULL;
+ int debug_calls = 0;
+ int dir_cache_timeout = -1;
+ int do_fork = 1;
+ char *fuse_options = NULL;
-#define ADD_FUSE_ARG(str) \
- do { \
- fuse_argc ++; \
- fuse_argv = realloc (fuse_argv, (1+fuse_argc) * sizeof (char *)); \
- if (!fuse_argv) { \
- perror ("realloc"); \
- exit (EXIT_FAILURE); \
- } \
- fuse_argv[fuse_argc-1] = (str); \
- fuse_argv[fuse_argc] = NULL; \
- } while (0)
+ struct guestfs_mount_local_argv optargs;
/* LC_ALL=C is required so we can parse error messages. */
setenv ("LC_ALL", "C", 1);
@@ -1016,24 +165,12 @@ main (int argc, char *argv[])
sa.sa_flags = SA_RESTART;
sigaction (SIGPIPE, &sa, NULL);
- /* Various initialization. */
- init_dir_caches ();
-
g = guestfs_create ();
if (g == NULL) {
fprintf (stderr, _("guestfs_create: failed to create handle\n"));
exit (EXIT_FAILURE);
}
- guestfs_set_recovery_proc (g, 0);
-
- ADD_FUSE_ARG (program_name);
- /* MUST be single-threaded. You cannot have two threads accessing the
- * same libguestfs handle, and opening more than one handle is likely
- * to be very expensive.
- */
- ADD_FUSE_ARG ("-s");
-
for (;;) {
c = getopt_long (argc, argv, options, long_options, &option_index);
if (c == -1) break;
@@ -1089,8 +226,7 @@ main (int argc, char *argv[])
break;
case 'o':
- ADD_FUSE_ARG ("-o");
- ADD_FUSE_ARG (optarg);
+ fuse_opt_add_opt_escaped (&fuse_options, optarg);
break;
case 'r':
@@ -1111,9 +247,8 @@ main (int argc, char *argv[])
case 'x':
OPTION_x;
- ADD_FUSE_ARG ("-f");
- guestfs_set_recovery_proc (g, 1);
- trace_calls = 1;
+ debug_calls = 1;
+ do_fork = 0;
break;
case HELP_OPTION:
@@ -1181,6 +316,9 @@ main (int argc, char *argv[])
exit (EXIT_FAILURE);
}
+ /* If we're forking, we can't use the recovery process. */
+ guestfs_set_recovery_proc (g, !do_fork);
+
/* Do the guest drives and mountpoints. */
add_drives (drvs, 'a');
if (guestfs_launch (g) == -1)
@@ -1196,36 +334,71 @@ main (int argc, char *argv[])
if (guestfs_umask (g, 0) == -1)
exit (EXIT_FAILURE);
+ optargs.bitmask = 0;
+ if (read_only) {
+ optargs.bitmask |= GUESTFS_MOUNT_LOCAL_READONLY_BITMASK;
+ optargs.readonly = 1;
+ }
+ if (debug_calls) {
+ optargs.bitmask |= GUESTFS_MOUNT_LOCAL_DEBUGCALLS_BITMASK;
+ optargs.debugcalls = 1;
+ }
+ if (dir_cache_timeout > 0) {
+ optargs.bitmask |= GUESTFS_MOUNT_LOCAL_CACHETIMEOUT_BITMASK;
+ optargs.cachetimeout = dir_cache_timeout;
+ }
+ if (fuse_options != NULL) {
+ optargs.bitmask |= GUESTFS_MOUNT_LOCAL_OPTIONS_BITMASK;
+ optargs.options = fuse_options;
+ }
+
+ if (guestfs_mount_local_argv (g, argv[optind], &optargs) == -1)
+ exit (EXIT_FAILURE);
+
/* At the last minute, remove the libguestfs error handler. In code
* above this point, the default error handler has been used which
- * sends all errors to stderr. Now before entering FUSE itself we
- * want to silence errors so we can convert them (see error()
- * function above).
+ * sends all errors to stderr. From now on, the FUSE code will
+ * convert errors into error codes (errnos) when appropriate.
*/
guestfs_set_error_handler (g, NULL, NULL);
- /* Finish off FUSE args. */
- ADD_FUSE_ARG (argv[optind]);
+ /* Daemonize. */
+ if (do_fork) {
+ pid_t pid;
+ int fd;
+
+ pid = fork ();
+ if (pid == -1) {
+ perror ("fork");
+ exit (EXIT_FAILURE);
+ }
+
+ if (pid != 0) /* parent */
+ _exit (EXIT_SUCCESS);
+
+ /* Emulate what old fuse_daemonize used to do. */
+ if (setsid () == -1) {
+ perror ("setsid");
+ exit (EXIT_FAILURE);
+ }
- /*
- It says about the line containing the for-statement:
- error: assuming signed overflow does not occur when simplifying conditional to
constant [-Wstrict-overflow]
+ ignore_value (chdir ("/"));
- if (verbose) {
- fprintf (stderr, "guestmount: invoking FUSE with args [");
- for (i = 0; i < fuse_argc; ++i) {
- if (i > 0) fprintf (stderr, ", ");
- fprintf (stderr, "%s", fuse_argv[i]);
+ fd = open ("/dev/null", O_RDWR);
+ if (fd >= 0) {
+ dup2 (fd, 0);
+ dup2 (fd, 1);
+ dup2 (fd, 2);
+ if (fd > 2)
+ close (fd);
}
- fprintf (stderr, "]\n");
}
- */
- r = fuse_main (fuse_argc, (char **) fuse_argv, &fg_operations, NULL);
+ /* Main loop. */
+ r = guestfs_mount_local_run (g);
/* Cleanup. */
guestfs_close (g);
- free_dir_caches ();
exit (r == 0 ? EXIT_SUCCESS : EXIT_FAILURE);
}
diff --git a/fuse/guestmount.h b/fuse/guestmount.h
index 81d026d..a6249ec 100644
--- a/fuse/guestmount.h
+++ b/fuse/guestmount.h
@@ -50,6 +50,4 @@ bad_cast (char const *s)
return (char *) s;
}
-extern int verbose;
-
#endif /* GUESTMOUNT_H_ */
diff --git a/generator/generator_actions.ml b/generator/generator_actions.ml
index f74db2d..24db395 100644
--- a/generator/generator_actions.ml
+++ b/generator/generator_actions.ml
@@ -1663,6 +1663,65 @@ This function must be called before
C<guestfs_launch>.");
"\
This returns the number of virtual CPUs assigned to the appliance.");
+ ("mount_local", (RErr, [String "localmountpoint"], [OBool
"readonly"; OString "options"; OInt "cachetimeout"; OBool
"debugcalls"]), -1, [],
+ [], (* tests in fuse subdirectory *)
+ "mount on the local filesystem",
+ "\
+This call exports the libguestfs-accessible filesystem to
+a local mountpoint (directory) called C<localmountpoint>.
+Ordinary reads and writes to files and directories under
+C<localmountpoint> are redirected through libguestfs.
+
+If the optional C<readonly> flag is set to true, then
+writes to the filesystem return error C<EROFS>.
+
+C<options> is a comma-separated list of mount options.
+See L<guestmount(1)> for some useful options.
+
+C<cachetimeout> sets the timeout (in seconds) for cached directory
+entries. The default is 60 seconds. See L<guestmount(1)>
+for further information.
+
+If C<debugcalls> is set to true, then additional debugging
+information is generated for every FUSE call.
+
+When C<guestfs_mount_local> returns, the filesystem is ready,
+but is not processing requests (access to it will block). You
+have to call C<guestfs_mount_local_run> to run the main loop.
+
+See L<guestfs(3)/MOUNT LOCAL> for full documentation.");
+
+ ("mount_local_run", (RErr, [], []), -1, [],
+ [],
+ "run main loop of mount on the local filesystem",
+ "\
+Run the main loop which translates kernel calls to libguestfs
+calls.
+
+This should only be called after C<guestfs_mount_local>
+returns successfully. The call will not return until the
+filesystem is unmounted.
+
+B<Note> you must I<not> make concurrent libguestfs calls
+on the same handle from another thread,
+with the exception of C<guestfs_umount_local>.
+
+You may call this from a different thread than the one which
+called C<guestfs_mount_local>, subject to the usual rules
+for threads and libguestfs (see
+L<guestfs(3)/MULTIPLE HANDLES AND MULTIPLE THREADS>).
+
+See L<guestfs(3)/MOUNT LOCAL> for full documentation.");
+
+ ("umount_local", (RErr, [], [OBool "retry"]), -1, [],
+ [], (* tests in fuse subdirectory *)
+ "unmount a locally mounted filesystem",
+ "\
+If libguestfs is exporting the filesystem on a local
+mountpoint, then this unmounts it.
+
+See L<guestfs(3)/MOUNT LOCAL> for full documentation.");
+
]
(* daemon_functions are any functions which cause some action
diff --git a/gobject/Makefile.inc b/gobject/Makefile.inc
index b936dfe..ec6577d 100644
--- a/gobject/Makefile.inc
+++ b/gobject/Makefile.inc
@@ -41,6 +41,8 @@ guestfs_gobject_headers=\
guestfs-gobject-optargs-add_drive_opts.h \
guestfs-gobject-optargs-add_domain.h \
guestfs-gobject-optargs-inspect_get_icon.h \
+ guestfs-gobject-optargs-mount_local.h \
+ guestfs-gobject-optargs-umount_local.h \
guestfs-gobject-optargs-mkfs_opts.h \
guestfs-gobject-optargs-mount_9p.h \
guestfs-gobject-optargs-ntfsresize_opts.h \
@@ -78,6 +80,8 @@ guestfs_gobject_sources=\
guestfs-gobject-optargs-add_drive_opts.c \
guestfs-gobject-optargs-add_domain.c \
guestfs-gobject-optargs-inspect_get_icon.c \
+ guestfs-gobject-optargs-mount_local.c \
+ guestfs-gobject-optargs-umount_local.c \
guestfs-gobject-optargs-mkfs_opts.c \
guestfs-gobject-optargs-mount_9p.c \
guestfs-gobject-optargs-ntfsresize_opts.c \
diff --git a/po/POTFILES.in b/po/POTFILES.in
index 620d68f..7cd55ca 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -129,8 +129,44 @@ fish/tilde.c
fish/time.c
fish/virt.c
format/format.c
-fuse/dircache.c
fuse/guestmount.c
+gobject/guestfs-gobject-optargs-add_domain.c
+gobject/guestfs-gobject-optargs-add_drive_opts.c
+gobject/guestfs-gobject-optargs-btrfs_filesystem_resize.c
+gobject/guestfs-gobject-optargs-compress_device_out.c
+gobject/guestfs-gobject-optargs-compress_out.c
+gobject/guestfs-gobject-optargs-copy_device_to_device.c
+gobject/guestfs-gobject-optargs-copy_device_to_file.c
+gobject/guestfs-gobject-optargs-copy_file_to_device.c
+gobject/guestfs-gobject-optargs-copy_file_to_file.c
+gobject/guestfs-gobject-optargs-e2fsck.c
+gobject/guestfs-gobject-optargs-inspect_get_icon.c
+gobject/guestfs-gobject-optargs-md_create.c
+gobject/guestfs-gobject-optargs-mkfs_opts.c
+gobject/guestfs-gobject-optargs-mount_9p.c
+gobject/guestfs-gobject-optargs-mount_local.c
+gobject/guestfs-gobject-optargs-ntfsclone_out.c
+gobject/guestfs-gobject-optargs-ntfsfix.c
+gobject/guestfs-gobject-optargs-ntfsresize_opts.c
+gobject/guestfs-gobject-optargs-test0.c
+gobject/guestfs-gobject-optargs-tune2fs.c
+gobject/guestfs-gobject-optargs-umount_local.c
+gobject/guestfs-gobject-session.c
+gobject/guestfs-gobject-struct-application.c
+gobject/guestfs-gobject-struct-dirent.c
+gobject/guestfs-gobject-struct-inotify_event.c
+gobject/guestfs-gobject-struct-int_bool.c
+gobject/guestfs-gobject-struct-isoinfo.c
+gobject/guestfs-gobject-struct-lvm_lv.c
+gobject/guestfs-gobject-struct-lvm_pv.c
+gobject/guestfs-gobject-struct-lvm_vg.c
+gobject/guestfs-gobject-struct-mdstat.c
+gobject/guestfs-gobject-struct-partition.c
+gobject/guestfs-gobject-struct-stat.c
+gobject/guestfs-gobject-struct-statvfs.c
+gobject/guestfs-gobject-struct-version.c
+gobject/guestfs-gobject-struct-xattr.c
+gobject/guestfs-gobject-tristate.c
gobject/guestfs-gobject.c
inspector/virt-inspector.c
java/com_redhat_et_libguestfs_GuestFS.c
@@ -155,6 +191,7 @@ src/errnostring.c
src/errnostring_gperf.c
src/events.c
src/filearch.c
+src/fuse.c
src/guestfs.c
src/inspect.c
src/inspect_apps.c
diff --git a/src/Makefile.am b/src/Makefile.am
index b91ffc1..a7fcbac 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -1,5 +1,5 @@
# libguestfs
-# Copyright (C) 2010 Red Hat Inc.
+# Copyright (C) 2010-2012 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
@@ -139,6 +139,7 @@ libguestfs_la_SOURCES = \
listfs.c \
match.c \
proto.c \
+ fuse.c \
virt.c \
libguestfs.syms
@@ -169,6 +170,12 @@ libguestfs_la_CFLAGS = \
libguestfs_la_CPPFLAGS = -I$(top_srcdir)/gnulib/lib -I$(top_builddir)/gnulib/lib
+if HAVE_FUSE
+# XXX Unfortunately FUSE_CFLAGS defines _FILE_OFFSET_BITS=64.
+libguestfs_la_CFLAGS += $(FUSE_CFLAGS)
+libguestfs_la_LIBADD += $(FUSE_LIBS) -lulockmgr
+endif
+
if HAVE_RPCGEN
guestfs_protocol.c: guestfs_protocol.x
rm -f $@-t $@-t2
diff --git a/src/fuse.c b/src/fuse.c
new file mode 100644
index 0000000..f233e37
--- /dev/null
+++ b/src/fuse.c
@@ -0,0 +1,1523 @@
+/* libguestfs
+ * Copyright (C) 2009-2012 Red Hat Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+
+/* See <attr/xattr.h> */
+#ifndef ENOATTR
+#define ENOATTR ENODATA
+#endif
+
+#define FUSE_USE_VERSION 26
+
+#if HAVE_FUSE
+#include <fuse.h>
+#include <fuse_lowlevel.h>
+#endif
+
+#include "cloexec.h"
+#include "glthread/lock.h"
+#include "hash.h"
+#include "hash-pjw.h"
+
+#include "guestfs.h"
+#include "guestfs-internal.h"
+#include "guestfs-internal-actions.h"
+#include "guestfs_protocol.h"
+
+#if HAVE_FUSE
+
+/* Functions handling the directory cache. */
+static int init_dir_caches (guestfs_h *);
+static void free_dir_caches (guestfs_h *);
+static void dir_cache_remove_all_expired (guestfs_h *, time_t now);
+static void dir_cache_invalidate (guestfs_h *, const char *path);
+static int lsc_insert (guestfs_h *, const char *path, const char *name, time_t now,
struct stat const *statbuf);
+static int xac_insert (guestfs_h *, const char *path, const char *name, time_t now,
struct guestfs_xattr_list *xattrs);
+static int rlc_insert (guestfs_h *, const char *path, const char *name, time_t now, char
*link);
+static const struct stat *lsc_lookup (guestfs_h *, const char *pathname);
+static const struct guestfs_xattr_list *xac_lookup (guestfs_h *, const char *pathname);
+static const char *rlc_lookup (guestfs_h *, const char *pathname);
+
+/* This lock protects access to g->localmountpoint. */
+gl_lock_define_initialized (static, mount_local_lock);
+
+#define DECL_G() guestfs_h *g = fuse_get_context()->private_data
+#define DEBUG_CALL(fs,...) \
+ if (g->ml_debug_calls) { \
+ debug (g, \
+ "%s: %s (" fs ")\n",
\
+ g->localmountpoint, __func__, ## __VA_ARGS__); \
+ }
+
+#define RETURN_ERRNO return -guestfs_last_errno (g)
+
+static struct guestfs_xattr_list *
+copy_xattr_list (const struct guestfs_xattr *first, size_t num)
+{
+ struct guestfs_xattr_list *xattrs;
+
+ xattrs = malloc (sizeof *xattrs);
+ if (xattrs == NULL) {
+ perror ("malloc");
+ return NULL;
+ }
+
+ xattrs->len = num;
+ xattrs->val = malloc (num * sizeof (struct guestfs_xattr));
+ if (xattrs->val == NULL) {
+ perror ("malloc");
+ free (xattrs);
+ return NULL;
+ }
+
+ size_t i;
+ for (i = 0; i < num; ++i) {
+ xattrs->val[i].attrname = strdup (first[i].attrname);
+ xattrs->val[i].attrval_len = first[i].attrval_len;
+ xattrs->val[i].attrval = malloc (first[i].attrval_len);
+ memcpy (xattrs->val[i].attrval, first[i].attrval, first[i].attrval_len);
+ }
+
+ return xattrs;
+}
+
+static int
+mount_local_readdir (const char *path, void *buf, fuse_fill_dir_t filler,
+ off_t offset, struct fuse_file_info *fi)
+{
+ DECL_G ();
+ DEBUG_CALL ("%s, %p, %ld", path, buf, (long) offset);
+
+ time_t now;
+ time (&now);
+
+ dir_cache_remove_all_expired (g, now);
+
+ struct guestfs_dirent_list *ents;
+
+ ents = guestfs_readdir (g, path);
+ if (ents == NULL)
+ RETURN_ERRNO;
+
+ size_t i;
+ for (i = 0; i < ents->len; ++i) {
+ struct stat stat;
+ memset (&stat, 0, sizeof stat);
+
+ stat.st_ino = ents->val[i].ino;
+ switch (ents->val[i].ftyp) {
+ case 'b': stat.st_mode = S_IFBLK; break;
+ case 'c': stat.st_mode = S_IFCHR; break;
+ case 'd': stat.st_mode = S_IFDIR; break;
+ case 'f': stat.st_mode = S_IFIFO; break;
+ case 'l': stat.st_mode = S_IFLNK; break;
+ case 'r': stat.st_mode = S_IFREG; break;
+ case 's': stat.st_mode = S_IFSOCK; break;
+ case 'u':
+ case '?':
+ default: stat.st_mode = 0;
+ }
+
+ /* Copied from the example, which also ignores 'offset'. I'm
+ * not quite sure how this is ever supposed to work on large
+ * directories. XXX
+ */
+ if (filler (buf, ents->val[i].name, &stat, 0))
+ break;
+ }
+
+ /* Now prepopulate the directory caches. This step is just an
+ * optimization, don't worry if it fails.
+ */
+ char **names = malloc ((ents->len + 1) * sizeof (char *));
+ if (names) {
+ for (i = 0; i < ents->len; ++i)
+ names[i] = ents->val[i].name;
+ names[i] = NULL;
+
+ struct guestfs_stat_list *ss = guestfs_lstatlist (g, path, names);
+ if (ss) {
+ for (i = 0; i < ss->len; ++i) {
+ if (ss->val[i].ino >= 0) {
+ struct stat statbuf;
+
+ statbuf.st_dev = ss->val[i].dev;
+ statbuf.st_ino = ss->val[i].ino;
+ statbuf.st_mode = ss->val[i].mode;
+ statbuf.st_nlink = ss->val[i].nlink;
+ statbuf.st_uid = ss->val[i].uid;
+ statbuf.st_gid = ss->val[i].gid;
+ statbuf.st_rdev = ss->val[i].rdev;
+ statbuf.st_size = ss->val[i].size;
+ statbuf.st_blksize = ss->val[i].blksize;
+ statbuf.st_blocks = ss->val[i].blocks;
+ statbuf.st_atime = ss->val[i].atime;
+ statbuf.st_mtime = ss->val[i].mtime;
+ statbuf.st_ctime = ss->val[i].ctime;
+
+ lsc_insert (g, path, names[i], now, &statbuf);
+ }
+ }
+ guestfs_free_stat_list (ss);
+ }
+
+ struct guestfs_xattr_list *xattrs = guestfs_lxattrlist (g, path, names);
+ if (xattrs) {
+ size_t ni, num;
+ struct guestfs_xattr *first;
+ struct guestfs_xattr_list *copy;
+ for (i = 0, ni = 0; i < xattrs->len; ++i, ++ni) {
+ /* assert (strlen (xattrs->val[i].attrname) == 0); */
+ if (xattrs->val[i].attrval_len > 0) {
+ ++i;
+ first = &xattrs->val[i];
+ num = 0;
+ for (; i < xattrs->len && strlen (xattrs->val[i].attrname)
> 0; ++i)
+ num++;
+
+ copy = copy_xattr_list (first, num);
+ if (copy)
+ xac_insert (g, path, names[ni], now, copy);
+
+ i--;
+ }
+ }
+ guestfs_free_xattr_list (xattrs);
+ }
+
+ char **links = guestfs_readlinklist (g, path, names);
+ if (links) {
+ for (i = 0; names[i] != NULL; ++i) {
+ if (links[i][0])
+ /* Note that rlc_insert owns the string links[i] after this, */
+ rlc_insert (g, path, names[i], now, links[i]);
+ else
+ /* which is why we have to free links[i] here. */
+ free (links[i]);
+ }
+ free (links); /* free the array, not the strings */
+ }
+
+ free (names);
+ }
+
+ guestfs_free_dirent_list (ents);
+
+ return 0;
+}
+
+static int
+mount_local_getattr (const char *path, struct stat *statbuf)
+{
+ DECL_G ();
+ DEBUG_CALL ("%s, %p", path, statbuf);
+
+ const struct stat *buf;
+
+ buf = lsc_lookup (g, path);
+ if (buf) {
+ memcpy (statbuf, buf, sizeof *statbuf);
+ return 0;
+ }
+
+ struct guestfs_stat *r;
+
+ r = guestfs_lstat (g, path);
+ if (r == NULL)
+ RETURN_ERRNO;
+
+ statbuf->st_dev = r->dev;
+ statbuf->st_ino = r->ino;
+ statbuf->st_mode = r->mode;
+ statbuf->st_nlink = r->nlink;
+ statbuf->st_uid = r->uid;
+ statbuf->st_gid = r->gid;
+ statbuf->st_rdev = r->rdev;
+ statbuf->st_size = r->size;
+ statbuf->st_blksize = r->blksize;
+ statbuf->st_blocks = r->blocks;
+ statbuf->st_atime = r->atime;
+ statbuf->st_mtime = r->mtime;
+ statbuf->st_ctime = r->ctime;
+
+ guestfs_free_stat (r);
+
+ return 0;
+}
+
+/* Nautilus loves to use access(2) to test everything about a file,
+ * such as whether it's executable. Therefore treat this a lot like
+ * mount_local_getattr.
+ */
+static int
+mount_local_access (const char *path, int mask)
+{
+ DECL_G ();
+ DEBUG_CALL ("%s, %d", path, mask);
+
+ struct stat statbuf;
+ int r;
+
+ if (g->ml_read_only && (mask & W_OK))
+ return -EROFS;
+
+ r = mount_local_getattr (path, &statbuf);
+ if (r < 0 || mask == F_OK)
+ return r;
+
+ struct fuse_context *fuse = fuse_get_context ();
+ int ok = 1;
+
+ if (mask & R_OK)
+ ok = ok &&
+ ( fuse->uid == statbuf.st_uid ? statbuf.st_mode & S_IRUSR
+ : fuse->gid == statbuf.st_gid ? statbuf.st_mode & S_IRGRP
+ : statbuf.st_mode & S_IROTH);
+ if (mask & W_OK)
+ ok = ok &&
+ ( fuse->uid == statbuf.st_uid ? statbuf.st_mode & S_IWUSR
+ : fuse->gid == statbuf.st_gid ? statbuf.st_mode & S_IWGRP
+ : statbuf.st_mode & S_IWOTH);
+ if (mask & X_OK)
+ ok = ok &&
+ ( fuse->uid == statbuf.st_uid ? statbuf.st_mode & S_IXUSR
+ : fuse->gid == statbuf.st_gid ? statbuf.st_mode & S_IXGRP
+ : statbuf.st_mode & S_IXOTH);
+
+ return ok ? 0 : -EACCES;
+}
+
+static int
+mount_local_readlink (const char *path, char *buf, size_t size)
+{
+ DECL_G ();
+ DEBUG_CALL ("%s, %p, %zu", path, buf, size);
+
+ const char *r;
+ int free_it = 0;
+
+ r = rlc_lookup (g, path);
+ if (!r) {
+ r = guestfs_readlink (g, path);
+ if (r == NULL)
+ RETURN_ERRNO;
+ free_it = 1;
+ }
+
+ /* Note this is different from the real readlink(2) syscall. FUSE wants
+ * the string to be always nul-terminated, even if truncated.
+ */
+ size_t len = strlen (r);
+ if (len > size - 1)
+ len = size - 1;
+
+ memcpy (buf, r, len);
+ buf[len] = '\0';
+
+ if (free_it) {
+ char *tmp = (char *) r;
+ free (tmp);
+ }
+
+ return 0;
+}
+
+static int
+mount_local_mknod (const char *path, mode_t mode, dev_t rdev)
+{
+ DECL_G ();
+ DEBUG_CALL ("%s, 0%o, 0x%lx", path, mode, (long) rdev);
+
+ int r;
+
+ if (g->ml_read_only) return -EROFS;
+
+ dir_cache_invalidate (g, path);
+
+ r = guestfs_mknod (g, mode, major (rdev), minor (rdev), path);
+ if (r == -1)
+ RETURN_ERRNO;
+
+ return 0;
+}
+
+static int
+mount_local_mkdir (const char *path, mode_t mode)
+{
+ DECL_G ();
+ DEBUG_CALL ("%s, 0%o", path, mode);
+
+ int r;
+
+ if (g->ml_read_only) return -EROFS;
+
+ dir_cache_invalidate (g, path);
+
+ r = guestfs_mkdir_mode (g, path, mode);
+ if (r == -1)
+ RETURN_ERRNO;
+
+ return 0;
+}
+
+static int
+mount_local_unlink (const char *path)
+{
+ DECL_G ();
+ DEBUG_CALL ("%s", path);
+
+ int r;
+
+ if (g->ml_read_only) return -EROFS;
+
+ dir_cache_invalidate (g, path);
+
+ r = guestfs_rm (g, path);
+ if (r == -1)
+ RETURN_ERRNO;
+
+ return 0;
+}
+
+static int
+mount_local_rmdir (const char *path)
+{
+ DECL_G ();
+ DEBUG_CALL ("%s", path);
+
+ int r;
+
+ if (g->ml_read_only) return -EROFS;
+
+ dir_cache_invalidate (g, path);
+
+ r = guestfs_rmdir (g, path);
+ if (r == -1)
+ RETURN_ERRNO;
+
+ return 0;
+}
+
+static int
+mount_local_symlink (const char *from, const char *to)
+{
+ DECL_G ();
+ DEBUG_CALL ("%s, %s", from, to);
+
+ int r;
+
+ if (g->ml_read_only) return -EROFS;
+
+ dir_cache_invalidate (g, to);
+
+ r = guestfs_ln_s (g, from, to);
+ if (r == -1)
+ RETURN_ERRNO;
+
+ return 0;
+}
+
+static int
+mount_local_rename (const char *from, const char *to)
+{
+ DECL_G ();
+ DEBUG_CALL ("%s, %s", from, to);
+
+ int r;
+
+ if (g->ml_read_only) return -EROFS;
+
+ dir_cache_invalidate (g, from);
+ dir_cache_invalidate (g, to);
+
+ /* XXX It's not clear how close the 'mv' command is to the
+ * rename syscall. We might need to add the rename syscall
+ * to the guestfs(3) API.
+ */
+ r = guestfs_mv (g, from, to);
+ if (r == -1)
+ RETURN_ERRNO;
+
+ return 0;
+}
+
+static int
+mount_local_link (const char *from, const char *to)
+{
+ DECL_G ();
+ DEBUG_CALL ("%s, %s", from, to);
+
+ int r;
+
+ if (g->ml_read_only) return -EROFS;
+
+ dir_cache_invalidate (g, from);
+ dir_cache_invalidate (g, to);
+
+ r = guestfs_ln (g, from, to);
+ if (r == -1)
+ RETURN_ERRNO;
+
+ return 0;
+}
+
+static int
+mount_local_chmod (const char *path, mode_t mode)
+{
+ DECL_G ();
+ DEBUG_CALL ("%s, 0%o", path, mode);
+
+ int r;
+
+ if (g->ml_read_only) return -EROFS;
+
+ dir_cache_invalidate (g, path);
+
+ r = guestfs_chmod (g, mode, path);
+ if (r == -1)
+ RETURN_ERRNO;
+
+ return 0;
+}
+
+static int
+mount_local_chown (const char *path, uid_t uid, gid_t gid)
+{
+ DECL_G ();
+ DEBUG_CALL ("%s, %ld, %ld", path, (long) uid, (long) gid);
+
+ int r;
+
+ if (g->ml_read_only) return -EROFS;
+
+ dir_cache_invalidate (g, path);
+
+ r = guestfs_lchown (g, uid, gid, path);
+ if (r == -1)
+ RETURN_ERRNO;
+
+ return 0;
+}
+
+static int
+mount_local_truncate (const char *path, off_t size)
+{
+ DECL_G ();
+ DEBUG_CALL ("%s, %ld", path, (long) size);
+
+ int r;
+
+ if (g->ml_read_only) return -EROFS;
+
+ dir_cache_invalidate (g, path);
+
+ r = guestfs_truncate_size (g, path, size);
+ if (r == -1)
+ RETURN_ERRNO;
+
+ return 0;
+}
+
+static int
+mount_local_utimens (const char *path, const struct timespec ts[2])
+{
+ DECL_G ();
+ DEBUG_CALL ("%s, [{ %ld, %ld }, { %ld, %ld }]",
+ path, ts[0].tv_sec, ts[0].tv_nsec, ts[1].tv_sec, ts[1].tv_nsec);
+
+ int r;
+
+ if (g->ml_read_only) return -EROFS;
+
+ dir_cache_invalidate (g, path);
+
+ time_t atsecs = ts[0].tv_sec;
+ long atnsecs = ts[0].tv_nsec;
+ time_t mtsecs = ts[1].tv_sec;
+ long mtnsecs = ts[1].tv_nsec;
+
+#ifdef UTIME_NOW
+ if (atnsecs == UTIME_NOW)
+ atnsecs = -1;
+#endif
+#ifdef UTIME_OMIT
+ if (atnsecs == UTIME_OMIT)
+ atnsecs = -2;
+#endif
+#ifdef UTIME_NOW
+ if (mtnsecs == UTIME_NOW)
+ mtnsecs = -1;
+#endif
+#ifdef UTIME_OMIT
+ if (mtnsecs == UTIME_OMIT)
+ mtnsecs = -2;
+#endif
+
+ r = guestfs_utimens (g, path, atsecs, atnsecs, mtsecs, mtnsecs);
+ if (r == -1)
+ RETURN_ERRNO;
+
+ return 0;
+}
+
+/* All this function needs to do is to check that the requested open
+ * flags are valid. See the notes in <fuse/fuse.h>.
+ */
+static int
+mount_local_open (const char *path, struct fuse_file_info *fi)
+{
+ DECL_G ();
+ DEBUG_CALL ("%s, 0%o", path, fi->flags);
+
+ int flags = fi->flags & O_ACCMODE;
+
+ if (g->ml_read_only && flags != O_RDONLY)
+ return -EROFS;
+
+ return 0;
+}
+
+static int
+mount_local_read (const char *path, char *buf, size_t size, off_t offset,
+ struct fuse_file_info *fi)
+{
+ DECL_G ();
+ DEBUG_CALL ("%s, %p, %zu, %ld", path, buf, size, (long) offset);
+
+ char *r;
+ size_t rsize;
+
+ debug (g,
+ "mount_local_read: %s: size %zu offset %ju\n", path, size, offset);
+
+ /* The guestfs protocol limits size to somewhere over 2MB. We just
+ * reduce the requested size here accordingly and push the problem
+ * up to every user.
http://www.jwz.org/doc/worse-is-better.html
+ */
+ const size_t limit = 2 * 1024 * 1024;
+ if (size > limit)
+ size = limit;
+
+ r = guestfs_pread (g, path, size, offset, &rsize);
+ if (r == NULL)
+ RETURN_ERRNO;
+
+ /* This should never happen, but at least it stops us overflowing
+ * the output buffer if it does happen.
+ */
+ if (rsize > size)
+ rsize = size;
+
+ memcpy (buf, r, rsize);
+ free (r);
+
+ return rsize;
+}
+
+static int
+mount_local_write (const char *path, const char *buf, size_t size,
+ off_t offset, struct fuse_file_info *fi)
+{
+ DECL_G ();
+ DEBUG_CALL ("%s, %p, %zu, %ld", path, buf, size, (long) offset);
+
+ if (g->ml_read_only) return -EROFS;
+
+ dir_cache_invalidate (g, path);
+
+ /* See mount_local_read. */
+ const size_t limit = 2 * 1024 * 1024;
+ if (size > limit)
+ size = limit;
+
+ int r;
+ r = guestfs_pwrite (g, path, buf, size, offset);
+ if (r == -1)
+ RETURN_ERRNO;
+
+ return r;
+}
+
+static int
+mount_local_statfs (const char *path, struct statvfs *stbuf)
+{
+ DECL_G ();
+ DEBUG_CALL ("%s, %p", path, stbuf);
+
+ struct guestfs_statvfs *r;
+
+ r = guestfs_statvfs (g, path);
+ if (r == NULL)
+ RETURN_ERRNO;
+
+ stbuf->f_bsize = r->bsize;
+ stbuf->f_frsize = r->frsize;
+ stbuf->f_blocks = r->blocks;
+ stbuf->f_bfree = r->bfree;
+ stbuf->f_bavail = r->bavail;
+ stbuf->f_files = r->files;
+ stbuf->f_ffree = r->ffree;
+ stbuf->f_favail = r->favail;
+ stbuf->f_fsid = r->fsid;
+ stbuf->f_flag = r->flag;
+ stbuf->f_namemax = r->namemax;
+
+ guestfs_free_statvfs (r);
+
+ return 0;
+}
+
+static int
+mount_local_release (const char *path, struct fuse_file_info *fi)
+{
+ DECL_G ();
+ DEBUG_CALL ("%s", path);
+
+ /* Just a stub. This method is optional and can safely be left
+ * unimplemented.
+ */
+ return 0;
+}
+
+/* Emulate this by calling sync. */
+static int
+mount_local_fsync (const char *path, int isdatasync,
+ struct fuse_file_info *fi)
+{
+ DECL_G ();
+ DEBUG_CALL ("%s, %d", path, isdatasync);
+
+ int r;
+
+ r = guestfs_sync (g);
+ if (r == -1)
+ RETURN_ERRNO;
+
+ return 0;
+}
+
+static int
+mount_local_setxattr (const char *path, const char *name, const char *value,
+ size_t size, int flags)
+{
+ DECL_G ();
+ DEBUG_CALL ("%s, %s, %p, %zu", path, name, value, size);
+
+ int r;
+
+ if (g->ml_read_only) return -EROFS;
+
+ dir_cache_invalidate (g, path);
+
+ /* XXX Underlying guestfs(3) API doesn't understand the flags. */
+ r = guestfs_lsetxattr (g, name, value, size, path);
+ if (r == -1)
+ RETURN_ERRNO;
+
+ return 0;
+}
+
+/* The guestfs(3) API for getting xattrs is much easier to use
+ * than the real syscall. Unfortunately we now have to emulate
+ * the real syscall using that API :-(
+ */
+static int
+mount_local_getxattr (const char *path, const char *name, char *value,
+ size_t size)
+{
+ DECL_G ();
+ DEBUG_CALL ("%s, %s, %p, %zu", path, name, value, size);
+
+ const struct guestfs_xattr_list *xattrs;
+ int free_attrs = 0;
+
+ xattrs = xac_lookup (g, path);
+ if (xattrs == NULL) {
+ xattrs = guestfs_lgetxattrs (g, path);
+ if (xattrs == NULL)
+ RETURN_ERRNO;
+ free_attrs = 1;
+ }
+
+ /* Find the matching attribute (index in 'i'). */
+ ssize_t r;
+ size_t i;
+ for (i = 0; i < xattrs->len; ++i) {
+ if (STREQ (xattrs->val[i].attrname, name))
+ break;
+ }
+
+ if (i == xattrs->len) { /* not found */
+ r = -ENOATTR;
+ goto out;
+ }
+
+ /* The getxattr man page is unclear, but if value == NULL then we
+ * return the space required (the caller then makes a second syscall
+ * after allocating the required amount of space). If value != NULL
+ * then it's not clear what we should do, but it appears we should
+ * copy as much as possible and return -ERANGE if there's not enough
+ * space in the buffer.
+ */
+ size_t sz = xattrs->val[i].attrval_len;
+ if (value == NULL) {
+ r = sz;
+ goto out;
+ }
+
+ if (sz <= size)
+ r = sz;
+ else {
+ r = -ERANGE;
+ sz = size;
+ }
+ memcpy (value, xattrs->val[i].attrval, sz);
+
+out:
+ if (free_attrs)
+ guestfs_free_xattr_list ((struct guestfs_xattr_list *) xattrs);
+
+ return r;
+}
+
+/* Ditto as above. */
+static int
+mount_local_listxattr (const char *path, char *list, size_t size)
+{
+ DECL_G ();
+ DEBUG_CALL ("%s, %p, %zu", path, list, size);
+
+ const struct guestfs_xattr_list *xattrs;
+ int free_attrs = 0;
+
+ xattrs = xac_lookup (g, path);
+ if (xattrs == NULL) {
+ xattrs = guestfs_lgetxattrs (g, path);
+ if (xattrs == NULL)
+ RETURN_ERRNO;
+ free_attrs = 1;
+ }
+
+ /* Calculate how much space is required to hold the result. */
+ size_t space = 0;
+ size_t len;
+ size_t i;
+ for (i = 0; i < xattrs->len; ++i) {
+ len = strlen (xattrs->val[i].attrname) + 1;
+ space += len;
+ }
+
+ /* The listxattr man page is unclear, but if list == NULL then we
+ * return the space required (the caller then makes a second syscall
+ * after allocating the required amount of space). If list != NULL
+ * then it's not clear what we should do, but it appears we should
+ * copy as much as possible and return -ERANGE if there's not enough
+ * space in the buffer.
+ */
+ ssize_t r;
+ if (list == NULL) {
+ r = space;
+ goto out;
+ }
+
+ r = 0;
+ for (i = 0; i < xattrs->len; ++i) {
+ len = strlen (xattrs->val[i].attrname) + 1;
+ if (size >= len) {
+ memcpy (list, xattrs->val[i].attrname, len);
+ size -= len;
+ list += len;
+ r += len;
+ } else {
+ r = -ERANGE;
+ break;
+ }
+ }
+
+ out:
+ if (free_attrs)
+ guestfs_free_xattr_list ((struct guestfs_xattr_list *) xattrs);
+
+ return r;
+}
+
+static int
+mount_local_removexattr(const char *path, const char *name)
+{
+ DECL_G ();
+ DEBUG_CALL ("%s, %s", path, name);
+
+ int r;
+
+ if (g->ml_read_only) return -EROFS;
+
+ dir_cache_invalidate (g, path);
+
+ r = guestfs_lremovexattr (g, name, path);
+ if (r == -1)
+ RETURN_ERRNO;
+
+ return 0;
+}
+
+static struct fuse_operations mount_local_operations = {
+ .getattr = mount_local_getattr,
+ .access = mount_local_access,
+ .readlink = mount_local_readlink,
+ .readdir = mount_local_readdir,
+ .mknod = mount_local_mknod,
+ .mkdir = mount_local_mkdir,
+ .symlink = mount_local_symlink,
+ .unlink = mount_local_unlink,
+ .rmdir = mount_local_rmdir,
+ .rename = mount_local_rename,
+ .link = mount_local_link,
+ .chmod = mount_local_chmod,
+ .chown = mount_local_chown,
+ .truncate = mount_local_truncate,
+ .utimens = mount_local_utimens,
+ .open = mount_local_open,
+ .read = mount_local_read,
+ .write = mount_local_write,
+ .statfs = mount_local_statfs,
+ .release = mount_local_release,
+ .fsync = mount_local_fsync,
+ .setxattr = mount_local_setxattr,
+ .getxattr = mount_local_getxattr,
+ .listxattr = mount_local_listxattr,
+ .removexattr = mount_local_removexattr,
+};
+
+int
+guestfs__mount_local (guestfs_h *g, const char *localmountpoint,
+ const struct guestfs_mount_local_argv *optargs)
+{
+ const char *t;
+ struct fuse_args args = FUSE_ARGS_INIT (0, NULL);
+ struct fuse_chan *ch;
+ int fd;
+
+ /* You can only mount each handle in one place in one thread. */
+ gl_lock_lock (mount_local_lock);
+ t = g->localmountpoint;
+ gl_lock_unlock (mount_local_lock);
+ if (t) {
+ error (g, _("filesystem is already mounted in another thread"));
+ return -1;
+ }
+
+ if (optargs->bitmask & GUESTFS_MOUNT_LOCAL_READONLY_BITMASK)
+ g->ml_read_only = optargs->readonly;
+ else
+ g->ml_read_only = 0;
+ if (optargs->bitmask & GUESTFS_MOUNT_LOCAL_CACHETIMEOUT_BITMASK)
+ g->ml_dir_cache_timeout = optargs->cachetimeout;
+ else
+ g->ml_dir_cache_timeout = 60;
+ if (optargs->bitmask & GUESTFS_MOUNT_LOCAL_DEBUGCALLS_BITMASK)
+ g->ml_debug_calls = optargs->debugcalls;
+ else
+ g->ml_debug_calls = 0;
+
+ /* Initialize the directory caches in the handle. */
+ if (init_dir_caches (g) == -1)
+ return -1;
+
+ /* Create the FUSE 'args'. */
+ /* XXX we don't have a program name */
+ if (fuse_opt_add_arg (&args, "guestfs_mount_local") == -1) {
+ arg_error:
+ perrorf (g, _("fuse_opt_add_arg: %s"), localmountpoint);
+ fuse_opt_free_args (&args);
+ free_dir_caches (g);
+ return -1;
+ }
+
+ if (optargs->bitmask & GUESTFS_MOUNT_LOCAL_OPTIONS_BITMASK) {
+ if (fuse_opt_add_arg (&args, "-o") == -1 ||
+ fuse_opt_add_arg (&args, optargs->options) == -1)
+ goto arg_error;
+ }
+
+ /* Create the FUSE mountpoint. */
+ ch = fuse_mount (localmountpoint, &args);
+ if (ch == NULL) {
+ perrorf (g, _("fuse_mount: %s"), localmountpoint);
+ fuse_opt_free_args (&args);
+ free_dir_caches (g);
+ return -1;
+ }
+
+ /* Set F_CLOEXEC on the channel. XXX libfuse should do this. */
+ fd = fuse_chan_fd (ch);
+ if (fd >= 0)
+ set_cloexec_flag (fd, 1);
+
+ /* Create the FUSE handle. */
+ g->fuse = fuse_new (ch, &args,
+ &mount_local_operations, sizeof mount_local_operations,
+ g);
+ if (!g->fuse) {
+ perrorf (g, _("fuse_new: %s"), localmountpoint);
+ fuse_unmount (localmountpoint, ch);
+ fuse_opt_free_args (&args);
+ free_dir_caches (g);
+ return -1;
+ }
+
+ fuse_opt_free_args (&args);
+
+ /* Set g->localmountpoint in the handle. */
+ gl_lock_lock (mount_local_lock);
+ g->localmountpoint = localmountpoint;
+ gl_lock_unlock (mount_local_lock);
+
+ return 0;
+}
+
+int
+guestfs__mount_local_run (guestfs_h *g)
+{
+ int r, mounted;
+
+ gl_lock_lock (mount_local_lock);
+ mounted = g->localmountpoint != NULL;
+ gl_lock_unlock (mount_local_lock);
+
+ if (!mounted) {
+ error (g, _("you must call guestfs_mount_local first"));
+ return -1;
+ }
+
+ /* Enter the main loop. */
+ r = fuse_loop (g->fuse);
+ if (r != 0)
+ perrorf (g, _("fuse_loop: %s"), g->localmountpoint);
+
+ gl_lock_lock (mount_local_lock);
+ fuse_destroy (g->fuse); /* also destroys 'ch' */
+ free_dir_caches (g);
+ g->localmountpoint = NULL;
+ g->fuse = NULL;
+ gl_lock_unlock (mount_local_lock);
+
+ /* By inspection, I found that fuse_loop only returns 0 or -1, but
+ * don't rely on this in future.
+ */
+ return r == 0 ? 0 : -1;
+}
+
+static int do_fusermount (guestfs_h *g, const char *localmountpoint, int error_fd);
+
+int
+guestfs__umount_local (guestfs_h *g,
+ const struct guestfs_umount_local_argv *optargs)
+{
+ size_t i, tries;
+ char *localmountpoint;
+ char *fusermount_log = NULL;
+ int error_fd = -1;
+ int ret = -1;
+
+ /* How many times should we try the fusermount command? */
+ if (optargs->bitmask & GUESTFS_UMOUNT_LOCAL_RETRY_BITMASK)
+ tries = optargs->retry ? 5 : 1;
+ else
+ tries = 1;
+
+ /* Make a local copy of g->localmountpoint. It could be freed from
+ * under us by another thread, except when we are holding the lock.
+ */
+ gl_lock_lock (mount_local_lock);
+ if (g->localmountpoint)
+ localmountpoint = safe_strdup (g, g->localmountpoint);
+ else
+ localmountpoint = NULL;
+ gl_lock_unlock (mount_local_lock);
+
+ if (!localmountpoint) {
+ error (g, _("no filesystem is mounted"));
+ goto out;
+ }
+
+ /* Send all errors from fusermount to a temporary file. Only after
+ * all 'tries' have failed do we print the contents of this file. A
+ * temporary failure when retry == true will not cause any error.
+ */
+ fusermount_log = safe_asprintf (g, "%s/fusermount.log", g->tmpdir);
+ error_fd = open (fusermount_log,
+ O_RDWR|O_CREAT|O_TRUNC|O_NOCTTY /* not O_CLOEXEC */,
+ 0600);
+ if (error_fd == -1) {
+ perrorf (g, _("open: %s"), fusermount_log);
+ goto out;
+ }
+
+ for (i = 0; i < tries; ++i) {
+ int r;
+
+ r = do_fusermount (g, localmountpoint, error_fd);
+ if (r == -1)
+ goto out;
+ if (r) {
+ /* External fusermount succeeded. Note that the original thread
+ * is responsible for setting g->localmountpoint to NULL.
+ */
+ ret = 0;
+ break;
+ }
+
+ sleep (1);
+ }
+
+ if (ret == -1) { /* fusermount failed */
+ char error_message[4096];
+ ssize_t n;
+
+ /* Get the error message from the log file. */
+ if (lseek (error_fd, 0, SEEK_SET) >= 0 &&
+ (n = read (error_fd, error_message, sizeof error_message - 1)) > 0) {
+ while (n > 0 && error_message[n-1] == '\n')
+ n--;
+ error_message[n] = '\0';
+ } else {
+ snprintf (error_message, sizeof error_message,
+ "(fusermount error could not be preserved)");
+ }
+
+ error (g, _("fusermount failed: %s: %s"), localmountpoint, error_message);
+ goto out;
+ }
+
+ out:
+ if (error_fd >= 0) close (error_fd);
+ if (fusermount_log) {
+ unlink (fusermount_log);
+ free (fusermount_log);
+ }
+ free (localmountpoint);
+ return ret;
+}
+
+static int
+do_fusermount (guestfs_h *g, const char *localmountpoint, int error_fd)
+{
+ pid_t pid;
+ int status;
+
+ pid = fork ();
+ if (pid == -1) {
+ perrorf (g, "fork");
+ return -1;
+ }
+
+ if (pid == 0) { /* child */
+ /* Ensure stdout and stderr point to the error_fd. */
+ dup2 (error_fd, STDOUT_FILENO);
+ dup2 (error_fd, STDERR_FILENO);
+ close (error_fd);
+ execlp ("fusermount", "fusermount", "-u",
localmountpoint, NULL);
+ perror ("exec: fusermount");
+ _exit (EXIT_FAILURE);
+ }
+
+ /* Parent. */
+ if (waitpid (pid, &status, 0) == -1) {
+ perrorf (g, "waitpid");
+ return -1;
+ }
+
+ if (!WIFEXITED (status) || WEXITSTATUS (status) != EXIT_SUCCESS)
+ return 0; /* it failed to unmount the mountpoint */
+
+ return 1; /* unmount was successful */
+}
+
+/* Functions handling the directory cache.
+ *
+ * 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 int
+init_dir_caches (guestfs_h *g)
+{
+ g->lsc_ht = hash_initialize (1024, NULL, gen_hash, gen_compare, lsc_free);
+ g->xac_ht = hash_initialize (1024, NULL, gen_hash, gen_compare, xac_free);
+ g->rlc_ht = hash_initialize (1024, NULL, gen_hash, gen_compare, rlc_free);
+ if (!g->lsc_ht || !g->xac_ht || !g->rlc_ht) {
+ error (g, _("could not initialize dir cache hashtables"));
+ return -1;
+ }
+ return 0;
+}
+
+static void
+free_dir_caches (guestfs_h *g)
+{
+ hash_free (g->lsc_ht);
+ hash_free (g->xac_ht);
+ hash_free (g->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)
+ 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);
+}
+
+static void
+dir_cache_remove_all_expired (guestfs_h *g, time_t now)
+{
+ gen_remove_all_expired (g->lsc_ht, lsc_free, now);
+ gen_remove_all_expired (g->xac_ht, xac_free, now);
+ gen_remove_all_expired (g->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);
+
+ 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;
+}
+
+static int
+lsc_insert (guestfs_h *g,
+ 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 + g->ml_dir_cache_timeout;
+
+ return gen_replace (g->lsc_ht, entry, lsc_free);
+}
+
+static int
+xac_insert (guestfs_h *g,
+ 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 + g->ml_dir_cache_timeout;
+
+ return gen_replace (g->xac_ht, (struct lsc_entry *) entry, xac_free);
+}
+
+static int
+rlc_insert (guestfs_h *g,
+ 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 + g->ml_dir_cache_timeout;
+
+ return gen_replace (g->rlc_ht, (struct lsc_entry *) entry, rlc_free);
+}
+
+static const struct stat *
+lsc_lookup (guestfs_h *g, const char *pathname)
+{
+ const struct lsc_entry key = { .pathname = (char *) pathname };
+ struct lsc_entry *entry;
+ time_t now;
+
+ time (&now);
+
+ entry = hash_lookup (g->lsc_ht, &key);
+ if (entry && entry->timeout >= now)
+ return &entry->statbuf;
+ else
+ return NULL;
+}
+
+static const struct guestfs_xattr_list *
+xac_lookup (guestfs_h *g, const char *pathname)
+{
+ const struct xac_entry key = { .pathname = (char *) pathname };
+ struct xac_entry *entry;
+ time_t now;
+
+ time (&now);
+
+ entry = hash_lookup (g->xac_ht, &key);
+ if (entry && entry->timeout >= now)
+ return entry->xattrs;
+ else
+ return NULL;
+}
+
+static const char *
+rlc_lookup (guestfs_h *g, const char *pathname)
+{
+ const struct rlc_entry key = { .pathname = (char *) pathname };
+ struct rlc_entry *entry;
+ time_t now;
+
+ time (&now);
+
+ entry = hash_lookup (g->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 = (char *) pathname };
+ struct lsc_entry *entry;
+
+ entry = hash_delete (ht, &key);
+
+ freer (entry);
+}
+
+static void
+dir_cache_invalidate (guestfs_h *g, const char *path)
+{
+ lsc_remove (g->lsc_ht, path, lsc_free);
+ lsc_remove (g->xac_ht, path, xac_free);
+ lsc_remove (g->rlc_ht, path, rlc_free);
+}
+
+#else /* !HAVE_FUSE */
+
+int
+guestfs__mount_local (guestfs_h *g, const char *localmountpoint,
+ const struct guestfs_mount_local_argv *optargs)
+{
+ guestfs_error_errno (g, ENOTSUP, _("FUSE not supported"));
+ return -1;
+}
+
+int
+guestfs__mount_local_run (guestfs_h *g)
+{
+ guestfs_error_errno (g, ENOTSUP, _("FUSE not supported"));
+ return -1;
+}
+
+int
+guestfs__umount_local (guestfs_h *g,
+ const struct guestfs_umount_local_argv *optargs)
+{
+ guestfs_error_errno (g, ENOTSUP, _("FUSE not supported"));
+ return -1;
+}
+
+#endif /* !HAVE_FUSE */
diff --git a/src/guestfs-internal.h b/src/guestfs-internal.h
index 1943f1a..c6aeb33 100644
--- a/src/guestfs-internal.h
+++ b/src/guestfs-internal.h
@@ -24,6 +24,8 @@
#include <pcre.h>
+#include "hash.h"
+
#ifndef O_CLOEXEC
#define O_CLOEXEC 0
#endif
@@ -248,6 +250,18 @@ struct guestfs_h
* matter for this case because we only care if it is != 0.
*/
int user_cancel;
+
+ /* These fields are used by guestfs_mount_local. Only valid when
+ * g->localmountpoint is not NULL.
+ */
+#if HAVE_FUSE
+ const char *localmountpoint;
+ struct fuse *fuse; /* FUSE handle. */
+ int ml_dir_cache_timeout; /* Directory cache timeout. */
+ Hash_table *lsc_ht, *xac_ht, *rlc_ht; /* Directory cache. */
+ int ml_read_only; /* If mounted read-only. */
+ int ml_debug_calls; /* Extra debug info on each FUSE call. */
+#endif
};
/* Per-filesystem data stored for inspect_os. */
diff --git a/src/guestfs.pod b/src/guestfs.pod
index 531d123..05f5c74 100644
--- a/src/guestfs.pod
+++ b/src/guestfs.pod
@@ -233,6 +233,10 @@ language bindings turn these errors into exceptions).
File writes are affected by the per-handle umask, set by calling
L</guestfs_umask> and defaulting to 022. See L</UMASK>.
+Since libguestfs 1.18, it is possible to mount the libguestfs
+filesystem on a local directory, subject to some restrictions. See
+L</MOUNT LOCAL> below.
+
=head2 PARTITIONING
Libguestfs contains API calls to read, create and modify partition
@@ -562,6 +566,54 @@ Then close the mapper device by calling
L</guestfs_luks_close> on the C</dev/mapper/mapname>
device (I<not> the underlying encrypted block device).
+=head2 MOUNT LOCAL
+
+In libguestfs E<ge> 1.18, it is possible to mount the libguestfs
+filesystem on a local directory and access it using ordinary POSIX
+calls and programs.
+
+Availability of this is subject to a number of restrictions: it
+requires FUSE (the Filesystem in USErspace), and libfuse must also
+have been available when libguestfs was compiled. FUSE may require
+that a kernel module is loaded, and it may be necessary to add the
+current user to a special C<fuse> group. See the documentation for
+your distribution and L<http://fuse.sf.net> for further information.
+
+The call to mount the libguestfs filesystem on a local directory is
+L</guestfs_mount_local> (q.v.) followed by L</guestfs_mount_local_run>.
+The latter does not return until you unmount the filesystem.
+The reason is that the call enters the FUSE main loop and processes
+kernel requests, turning them into libguestfs calls. An alternative
+design would have been to create a background thread to do this, but
+libguestfs doesn't require pthreads. This way is also more flexible:
+for example the user can create another thread for
+L</guestfs_mount_local_run>.
+
+L</guestfs_mount_local> needs a certain amount of time to set up the
+mountpoint. The mountpoint is not ready to use until the call
+returns. At this point, accesses to the filesystem will block
+until the main loop is entered (ie. L</guestfs_mount_local_run>).
+So if you need to start another process to access the filesystem,
+put the fork between L</guestfs_mount_local> and
+L</guestfs_mount_local_run>.
+
+=head3 MOUNT LOCAL COMPATIBILITY
+
+Since local mounting was only added in libguestfs 1.18, and may not
+be available even in these builds, you should consider writing code
+so that it doesn't depend on this feature, and can fall back to
+using libguestfs file system calls.
+
+If libguestfs was compiled without support for L</guestfs_mount_local>
+then calling it will return an error with errno set to C<ENOTSUP> (see
+L</guestfs_last_errno>).
+
+=head3 MOUNT LOCAL PERFORMANCE
+
+Libguestfs on top of FUSE performs quite poorly. For best performance
+do not use it. Use ordinary libguestfs filesystem calls, upload,
+download etc. instead.
+
=head2 INSPECTION
Libguestfs has APIs for inspecting an unknown disk image to find out
--
1.7.9.3