diff options
-rw-r--r-- | .gitignore | 3 | ||||
-rw-r--r-- | HACKING | 3 | ||||
-rw-r--r-- | Makefile.am | 5 | ||||
-rw-r--r-- | README | 2 | ||||
-rw-r--r-- | TODO | 35 | ||||
-rwxr-xr-x | bootstrap | 2 | ||||
-rw-r--r-- | configure.ac | 11 | ||||
-rw-r--r-- | fuse/Makefile.am | 62 | ||||
-rw-r--r-- | fuse/dircache.c | 408 | ||||
-rw-r--r-- | fuse/dircache.h | 44 | ||||
-rw-r--r-- | fuse/guestmount.c | 1163 | ||||
-rw-r--r-- | fuse/guestmount.pod | 205 | ||||
-rw-r--r-- | m4/.gitignore | 2 | ||||
-rw-r--r-- | po/POTFILES.in | 2 |
14 files changed, 1920 insertions, 27 deletions
@@ -54,6 +54,8 @@ fish/completion.c fish/guestfish fish/rc_protocol.c fish/rc_protocol.h +fuse/guestmount +fuse/guestmount.1 guestfish.1 guestfish-actions.pod guestfs.3 @@ -72,6 +74,7 @@ hivex/hivexget hivex/hivexml html/guestfish.1.html html/guestfs.3.html +html/guestmount.1.html html/hivex.3.html html/hivexget.1.html html/hivexml.1.html @@ -83,6 +83,9 @@ examples/ fish/ Guestfish (the command-line program / shell) +fuse/ + FUSE (userspace filesystem) built on top of libguestfs. + haskell/ Haskell bindings. diff --git a/Makefile.am b/Makefile.am index 0b905ca1..a4f4b0b9 100644 --- a/Makefile.am +++ b/Makefile.am @@ -34,6 +34,10 @@ if HAVE_TOOLS SUBDIRS += tools endif +if HAVE_FUSE +SUBDIRS += fuse +endif + if HAVE_OCAML SUBDIRS += ocaml ocaml/examples endif @@ -134,6 +138,7 @@ html/recipes.html: $(wildcard recipes/*.sh) $(wildcard recipes/*.html) $(wildcar HTMLFILES = \ html/guestfs.3.html \ html/guestfish.1.html \ + html/guestmount.1.html \ html/hivex.3.html \ html/hivexget.1.html \ html/hivexml.1.html \ @@ -52,6 +52,8 @@ Requirements - libxml2 +- (Optional) FUSE to build the FUSE module + - (Optional) Augeas (http://augeas.net/) - perldoc (pod2man, pod2text) to generate the manual pages and @@ -12,33 +12,16 @@ Python bindings Ideas for the Python bindings: https://www.redhat.com/archives/fedora-virt/2009-April/msg00114.html -FTP server or FUSE? -------------------- +FUSE API +-------- + +The API needs more test coverage, particularly lesser-used system +calls. -Originally we had intended to implement an NFS server inside the -appliance, which would allow the guest filesystems to be mounted on -the host, and large changes to be made. We eventually rejected the -idea of using NFS, partly because it requires root to mount -filesystems in the host, and partly because of problems handling UID -mappings between host and guest filesystem. - -Then we look at implementing an FTP server instead. FTP clients are -widely available for many languages, don't require root, and don't -have any UID mapping problems. However there is the problem of -getting the TCP connection into the guest, and that FTP requires a -secondary data connection either in or out of the guest (the NFS -situation is even more dire). - -Thirdly we looked at implementing a FUSE-based filesystem. This is -plausible - it could be implemented just by adding the additional FUSE -operations to the standard guestfs(3) API, and then implementing a -simple FUSE daemon. (The FUSE website has some very helpful -documentation and examples). I [RWMJ] am not particularly convinced -that a FUSE-based filesystem would really be useful to anyone, but am -prepared to accept patches if someone does all the work. - -See also the mountlo project: -http://sourceforge.net/project/showfiles.php?group_id=121684&package_id=150116 +The big unresolved issue is UID/GID mapping between guest filesystem +IDs and the host. It's not easy to automate this because you need +extra details about the guest itself in order to get to its +UID->username map (eg. /etc/passwd from the guest). BufferIn -------- @@ -63,6 +63,8 @@ closeout gitlog-to-changelog gnu-make gnumakefile +hash +hash-pjw ignore-value maintainer-makefile manywarnings diff --git a/configure.ac b/configure.ac index e11de0c3..fb538c3a 100644 --- a/configure.ac +++ b/configure.ac @@ -421,6 +421,13 @@ PKG_CHECK_MODULES([LIBXML2], [libxml-2.0]) AC_SUBST([LIBXML2_CFLAGS]) AC_SUBST([LIBXML2_LIBS]) +dnl FUSE is optional to build the FUSE module. +HAVE_FUSE=yes +PKG_CHECK_MODULES([FUSE],[fuse],,[ + HAVE_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$HAVE_FUSE" = "xyes"]) + dnl Check for OCaml (optional, for OCaml bindings). AC_PROG_OCAML AC_PROG_FINDLIB @@ -726,7 +733,8 @@ AC_CONFIG_FILES([Makefile libguestfs.pc gnulib/lib/Makefile gnulib/tests/Makefile - hivex/Makefile + hivex/Makefile + fuse/Makefile ocaml/META perl/Makefile.PL]) AC_OUTPUT @@ -756,6 +764,7 @@ if test "x$HAVE_INSPECTOR" = "x"; then echo "yes"; else echo "no"; fi echo -n "virt-* tools ........................ " if test "x$HAVE_TOOLS" = "x"; then echo "yes"; else echo "no"; fi echo "supermin appliance .................. $enable_supermin" +echo "FUSE filesystem ..................... $HAVE_FUSE" echo echo "If any optional component is configured 'no' when you expected 'yes'" echo "then you should check the preceeding messages." diff --git a/fuse/Makefile.am b/fuse/Makefile.am new file mode 100644 index 00000000..5d5ea309 --- /dev/null +++ b/fuse/Makefile.am @@ -0,0 +1,62 @@ +# libguestfs +# 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. + +EXTRA_DIST = fusexmp.c fusexmp_fh.c + +if HAVE_FUSE + +bin_PROGRAMS = guestmount + +guestmount_SOURCES = \ + dircache.c \ + dircache.h \ + guestmount.c + +guestmount_CFLAGS = \ + -I$(top_srcdir)/src -I$(top_builddir)/src \ + -I$(srcdir)/../gnulib/lib -I../gnulib/lib \ + -DGUESTFS_DEFAULT_PATH='"$(libdir)/guestfs"' \ + $(FUSE_CFLAGS) \ + $(WARN_CFLAGS) $(WERROR_CFLAGS) + +guestmount_LDADD = \ + $(FUSE_LIBS) -lulockmgr \ + $(top_builddir)/src/libguestfs.la \ + ../gnulib/lib/libgnu.la + +man_MANS = guestmount.1 + +guestmount.1: guestmount.pod + $(POD2MAN) \ + --section 1 \ + -c "Virtualization Support" \ + --name "guestmount" \ + --release "$(PACKAGE_NAME)-$(PACKAGE_VERSION)" \ + $< > $@-t; mv $@-t $@ + +noinst_DATA = \ + $(top_builddir)/html/guestmount.1.html + +$(top_builddir)/html/guestmount.1.html: guestmount.pod + mkdir -p $(top_builddir)/html + cd $(top_builddir) && pod2html \ + --css 'pod.css' \ + --htmldir html \ + --outfile html/guestmount.1.html \ + fuse/guestmount.pod + +endif diff --git a/fuse/dircache.c b/fuse/dircache.c new file mode 100644 index 00000000..545b9f38 --- /dev/null +++ b/fuse/dircache.c @@ -0,0 +1,408 @@ +/* guestmount - mount guests using libguestfs and FUSE + * Copyright (C) 2009 Red Hat Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * Derived from the example program 'fusexmp.c': + * Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu> + * + * This program can be distributed under the terms of the GNU GPL. + * See the file COPYING. + */ + +#include <config.h> + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> +#include <assert.h> +#include <sys/time.h> +#include <sys/types.h> + +#include <guestfs.h> + +#include "hash.h" +#include "hash-pjw.h" + +#include "dircache.h" + +extern int verbose; +extern int dir_cache_timeout; + +static inline char * +bad_cast (char const *s) +{ + return (char *) s; +} + +/* Note on attribute caching: FUSE can cache filesystem attributes for + * short periods of time (configurable via -o attr_timeout). It + * doesn't cache xattrs, and in any case FUSE caching doesn't solve + * the problem that we have to make a series of guestfs_lstat and + * guestfs_lgetxattr calls when we first list a directory (thus, many + * round trips). + * + * For this reason, we also implement a readdir cache here which is + * invoked when a readdir call is made. readdir is modified so that + * as well as reading the directory, it also requests all the stat + * structures, xattrs and readlinks of all entries in the directory, + * and these are added to the cache here (for a short, configurable + * period of time) in anticipation that they will be needed + * immediately afterwards, which is usually the case when the user is + * doing an "ls"-like operation. + * + * You can still use FUSE attribute caching on top of this mechanism + * if you like. + */ + +struct lsc_entry { /* lstat cache entry */ + char *pathname; /* full path to the file */ + time_t timeout; /* when this entry expires */ + struct stat statbuf; /* statbuf */ +}; + +struct xac_entry { /* xattr cache entry */ + /* NB first two fields must be same as lsc_entry */ + char *pathname; /* full path to the file */ + time_t timeout; /* when this entry expires */ + struct guestfs_xattr_list *xattrs; +}; + +struct rlc_entry { /* readlink cache entry */ + /* NB first two fields must be same as lsc_entry */ + char *pathname; /* full path to the file */ + time_t timeout; /* when this entry expires */ + char *link; +}; + +static size_t +gen_hash (void const *x, size_t table_size) +{ + struct lsc_entry const *p = x; + return hash_pjw (p->pathname, table_size); +} + +static bool +gen_compare (void const *x, void const *y) +{ + struct lsc_entry const *a = x; + struct lsc_entry const *b = y; + return strcmp (a->pathname, b->pathname) == 0; +} + +static void +lsc_free (void *x) +{ + if (x) { + struct lsc_entry *p = x; + + free (p->pathname); + free (p); + } +} + +static void +xac_free (void *x) +{ + if (x) { + struct xac_entry *p = x; + + guestfs_free_xattr_list (p->xattrs); + lsc_free (x); + } +} + +static void +rlc_free (void *x) +{ + if (x) { + struct rlc_entry *p = x; + + free (p->link); + lsc_free (x); + } +} + +static Hash_table *lsc_ht, *xac_ht, *rlc_ht; + +void +init_dir_caches (void) +{ + lsc_ht = hash_initialize (1024, NULL, gen_hash, gen_compare, lsc_free); + xac_ht = hash_initialize (1024, NULL, gen_hash, gen_compare, xac_free); + rlc_ht = hash_initialize (1024, NULL, gen_hash, gen_compare, rlc_free); + if (!lsc_ht || !xac_ht || !rlc_ht) { + fprintf (stderr, "guestmount: could not initialize dir cache hashtables\n"); + exit (1); + } +} + +void +free_dir_caches (void) +{ + hash_free (lsc_ht); + hash_free (xac_ht); + hash_free (rlc_ht); +} + +struct gen_remove_data { + time_t now; + Hash_table *ht; + Hash_data_freer freer; +}; + +static bool +gen_remove_if_expired (void *x, void *data) +{ + /* XXX hash_do_for_each was observed calling this function + * with x == NULL. + */ + if (x) { + struct lsc_entry *p = x; + struct gen_remove_data *d = data; + + if (p->timeout < d->now) { + if (verbose) + fprintf (stderr, "dir cache: expiring entry %p (%s)\n", + p, p->pathname); + d->freer (hash_delete (d->ht, x)); + } + } + + return 1; +} + +static void +gen_remove_all_expired (Hash_table *ht, Hash_data_freer freer, time_t now) +{ + struct gen_remove_data data; + data.now = now; + data.ht = ht; + data.freer = freer; + + /* Careful reading of the documentation to hash _seems_ to indicate + * that this is safe, _provided_ we use the default thresholds (in + * particular, no shrink threshold). + */ + hash_do_for_each (ht, gen_remove_if_expired, &data); +} + +void +dir_cache_remove_all_expired (time_t now) +{ + gen_remove_all_expired (lsc_ht, lsc_free, now); + gen_remove_all_expired (xac_ht, xac_free, now); + gen_remove_all_expired (rlc_ht, rlc_free, now); +} + +static int +gen_replace (Hash_table *ht, struct lsc_entry *new_entry, Hash_data_freer freer) +{ + struct lsc_entry *old_entry; + + old_entry = hash_delete (ht, new_entry); + freer (old_entry); + + if (verbose && old_entry) + fprintf (stderr, "dir cache: this entry replaced old entry %p (%s)\n", + old_entry, old_entry->pathname); + + old_entry = hash_insert (ht, new_entry); + if (old_entry == NULL) { + perror ("hash_insert"); + freer (new_entry); + return -1; + } + assert (old_entry == new_entry); + + return 0; +} + +int +lsc_insert (const char *path, const char *name, time_t now, + struct stat const *statbuf) +{ + struct lsc_entry *entry; + + entry = malloc (sizeof *entry); + if (entry == NULL) { + perror ("malloc"); + return -1; + } + + size_t len = strlen (path) + strlen (name) + 2; + entry->pathname = malloc (len); + if (entry->pathname == NULL) { + perror ("malloc"); + free (entry); + return -1; + } + if (strcmp (path, "/") == 0) + snprintf (entry->pathname, len, "/%s", name); + else + snprintf (entry->pathname, len, "%s/%s", path, name); + + memcpy (&entry->statbuf, statbuf, sizeof entry->statbuf); + + entry->timeout = now + dir_cache_timeout; + + if (verbose) + fprintf (stderr, "dir cache: inserting lstat entry %p (%s)\n", + entry, entry->pathname); + + return gen_replace (lsc_ht, entry, lsc_free); +} + +int +xac_insert (const char *path, const char *name, time_t now, + struct guestfs_xattr_list *xattrs) +{ + struct xac_entry *entry; + + entry = malloc (sizeof *entry); + if (entry == NULL) { + perror ("malloc"); + return -1; + } + + size_t len = strlen (path) + strlen (name) + 2; + entry->pathname = malloc (len); + if (entry->pathname == NULL) { + perror ("malloc"); + free (entry); + return -1; + } + if (strcmp (path, "/") == 0) + snprintf (entry->pathname, len, "/%s", name); + else + snprintf (entry->pathname, len, "%s/%s", path, name); + + entry->xattrs = xattrs; + + entry->timeout = now + dir_cache_timeout; + + if (verbose) + fprintf (stderr, "dir cache: inserting xattr entry %p (%s)\n", + entry, entry->pathname); + + return gen_replace (xac_ht, (struct lsc_entry *) entry, xac_free); +} + +int +rlc_insert (const char *path, const char *name, time_t now, + char *link) +{ + struct rlc_entry *entry; + + entry = malloc (sizeof *entry); + if (entry == NULL) { + perror ("malloc"); + return -1; + } + + size_t len = strlen (path) + strlen (name) + 2; + entry->pathname = malloc (len); + if (entry->pathname == NULL) { + perror ("malloc"); + free (entry); + return -1; + } + if (strcmp (path, "/") == 0) + snprintf (entry->pathname, len, "/%s", name); + else + snprintf (entry->pathname, len, "%s/%s", path, name); + + entry->link = link; + + entry->timeout = now + dir_cache_timeout; + + if (verbose) + fprintf (stderr, "dir cache: inserting readlink entry %p (%s)\n", + entry, entry->pathname); + + return gen_replace (rlc_ht, (struct lsc_entry *) entry, rlc_free); +} + +const struct stat * +lsc_lookup (const char *pathname) +{ + const struct lsc_entry key = { .pathname = bad_cast (pathname) }; + struct lsc_entry *entry; + time_t now; + + time (&now); + + entry = hash_lookup (lsc_ht, &key); + if (entry && entry->timeout >= now) + return &entry->statbuf; + else + return NULL; +} + +const struct guestfs_xattr_list * +xac_lookup (const char *pathname) +{ + const struct xac_entry key = { .pathname = bad_cast (pathname) }; + struct xac_entry *entry; + time_t now; + + time (&now); + + entry = hash_lookup (xac_ht, &key); + if (entry && entry->timeout >= now) + return entry->xattrs; + else + return NULL; +} + +const char * +rlc_lookup (const char *pathname) +{ + const struct rlc_entry key = { .pathname = bad_cast (pathname) }; + struct rlc_entry *entry; + time_t now; + + time (&now); + + entry = hash_lookup (rlc_ht, &key); + if (entry && entry->timeout >= now) + return entry->link; + else + return NULL; +} + +static void +lsc_remove (Hash_table *ht, const char *pathname, Hash_data_freer freer) +{ + const struct lsc_entry key = { .pathname = bad_cast (pathname) }; + struct lsc_entry *entry; + + entry = hash_delete (ht, &key); + + if (verbose) + fprintf (stderr, "dir cache: invalidating entry %p (%s)\n", + entry, entry->pathname); + + freer (entry); +} + +void +dir_cache_invalidate (const char *path) +{ + lsc_remove (lsc_ht, path, lsc_free); + lsc_remove (xac_ht, path, xac_free); + lsc_remove (rlc_ht, path, rlc_free); +} diff --git a/fuse/dircache.h b/fuse/dircache.h new file mode 100644 index 00000000..adc117ec --- /dev/null +++ b/fuse/dircache.h @@ -0,0 +1,44 @@ +/* guestmount - mount guests using libguestfs and FUSE + * Copyright (C) 2009 Red Hat Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * Derived from the example program 'fusexmp.c': + * Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu> + * + * This program can be distributed under the terms of the GNU GPL. + * See the file COPYING. + */ + +#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); + +#endif /* GUESTMOUNT_DIRCACHE_H */ diff --git a/fuse/guestmount.c b/fuse/guestmount.c new file mode 100644 index 00000000..ec3cc773 --- /dev/null +++ b/fuse/guestmount.c @@ -0,0 +1,1163 @@ +/* guestmount - mount guests using libguestfs and FUSE + * Copyright (C) 2009 Red Hat Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * Derived from the example program 'fusexmp.c': + * Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu> + * + * This program can be distributed under the terms of the GNU GPL. + * See the file COPYING. + */ + +#define FUSE_USE_VERSION 26 + +#include <config.h> + +#include <stdio.h> +#include <stdlib.h> +#include <stdint.h> +#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 <fuse.h> +#include <guestfs.h> + +#include "progname.h" + +#include "dircache.h" + +/* See <attr/xattr.h> */ +#ifndef ENOATTR +#define ENOATTR ENODATA +#endif + +#ifdef HAVE_GETTEXT +#include "gettext.h" +#define _(str) dgettext(PACKAGE, (str)) +//#define N_(str) dgettext(PACKAGE, (str)) +#else +#define _(str) str +//#define N_(str) str +#endif + +static inline char * +bad_cast (char const *s) +{ + return (char *) s; +} + +static guestfs_h *g = NULL; +static int read_only = 0; +int verbose = 0; +int dir_cache_timeout = 60; + +/* This is ugly: guestfs errors are strings, FUSE wants -errno. We + * have to do the conversion as best we can. + */ +#define MAX_ERRNO 256 + +static int +error (void) +{ + int i; + const char *err = guestfs_last_error (g); + + if (!err) + return -EINVAL; + + if (verbose) + fprintf (stderr, "%s\n", err); + + /* Add a few of our own ... */ + + /* This indicates guestfsd died. Translate into a hard EIO error. + * Arguably we could relaunch the guest if we hit this error. + */ + if (strstr (err, "call launch before using this function")) + return -EIO; + + /* See if it matches an errno string in the host. */ + for (i = 0; i < MAX_ERRNO; ++i) { + const char *e = strerror (i); + if (e && strstr (err, e) != NULL) + return -i; + } + + /* Too bad, return a generic error. */ + return -EINVAL; +} + +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) +{ + 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) +{ + 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) +{ + 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) +{ + 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) + free ((char *) r); + + return 0; +} + +static int +fg_mknod (const char *path, mode_t mode, dev_t 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) +{ + 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) +{ + 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) +{ + 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) +{ + int r; + + if (read_only) return -EROFS; + + dir_cache_invalidate (to); + + r = guestfs_ln_s (g, to, from); + if (r == -1) + return error (); + + return 0; +} + +static int +fg_rename (const char *from, const char *to) +{ + int r; + + if (read_only) return -EROFS; + + 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) +{ + int r; + + if (read_only) return -EROFS; + + dir_cache_invalidate (to); + + r = guestfs_ln (g, to, from); + if (r == -1) + return error (); + + return 0; +} + +static int +fg_chmod (const char *path, mode_t 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) +{ + 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) +{ + 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]) +{ + 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; + + if (atnsecs == UTIME_NOW) + atnsecs = -1; + if (atnsecs == UTIME_OMIT) + atnsecs = -2; + if (mtnsecs == UTIME_NOW) + mtnsecs = -1; + if (mtnsecs == UTIME_OMIT) + mtnsecs = -2; + + r = guestfs_utimens (g, path, atsecs, atnsecs, mtsecs, mtnsecs); + if (r == -1) + return error (); + + return 0; +} + +/* This call is quite hard to emulate through the guestfs(3) API. In + * one sense it's a little like access (see above) because it tests + * whether opening a file would succeed given the flags. But it also + * has side effects such as truncating the file if O_TRUNC is given. + * Therefore we need to emulate it ... painfully. + */ +static int +fg_open (const char *path, struct fuse_file_info *fi) +{ + int r, exists; + + if (fi->flags & O_WRONLY) { + if (read_only) + return -EROFS; + } + + exists = guestfs_exists (g, path); + if (exists == -1) + return error (); + + if (fi->flags & O_CREAT) { + if (read_only) + return -EROFS; + + dir_cache_invalidate (path); + + /* Exclusive? File must not exist already. */ + if (fi->flags & O_EXCL) { + if (exists) + return -EEXIST; + } + + /* Create? Touch it and optionally truncate it. */ + r = guestfs_touch (g, path); + if (r == -1) + return error (); + + if (fi->flags & O_TRUNC) { + r = guestfs_truncate (g, path); + if (r == -1) + return error (); + } + } else { + /* Not create, just check it exists. */ + if (!exists) + return -ENOENT; + } + + return 0; +} + +static int +fg_read (const char *path, char *buf, size_t size, off_t offset, + struct fuse_file_info *fi) +{ + char *r; + size_t rsize; + + if (verbose) + fprintf (stderr, "fg_read: %s: size %zu offset %zu\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) +{ + if (read_only) return -EROFS; + + dir_cache_invalidate (path); + + return -ENOSYS; /* XXX */ +} + +static int +fg_statfs (const char *path, struct statvfs *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) +{ + /* 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) +{ + 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) +{ + 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) +{ + 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; + } + + size_t i; + int r = -ENOATTR; + for (i = 0; i < xattrs->len; ++i) { + if (strcmp (xattrs->val[i].attrname, name) == 0) { + size_t sz = xattrs->val[i].attrval_len; + if (sz > size) + sz = size; + memcpy (value, xattrs->val[i].attrval, sz); + r = 0; + break; + } + } + + 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) +{ + 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; + } + + size_t i; + ssize_t copied = 0; + for (i = 0; i < xattrs->len; ++i) { + size_t len = strlen (xattrs->val[i].attrname) + 1; + if (size >= len) { + memcpy (list, xattrs->val[i].attrname, len); + size -= len; + list += len; + copied += len; + } else { + copied = -ERANGE; + break; + } + } + + if (free_attrs) + guestfs_free_xattr_list ((struct guestfs_xattr_list *) xattrs); + + return copied; +} + +static int +fg_removexattr(const char *path, const char *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, +}; + +struct drv { + struct drv *next; + char *filename; +}; + +struct mp { + struct mp *next; + char *device; + char *mountpoint; +}; + +static void add_drives (struct drv *); +static void mount_mps (struct mp *); + +static void __attribute__((noreturn)) +fuse_help (void) +{ + const char *tmp_argv[] = { program_name, "--help", NULL }; + fuse_main (2, (char **) tmp_argv, &fg_operations, NULL); + exit (0); +} + +static void __attribute__((noreturn)) +usage (int status) +{ + if (status != EXIT_SUCCESS) + fprintf (stderr, _("Try `%s --help' for more information.\n"), + program_name); + else { + fprintf (stdout, + _("%s: FUSE module for libguestfs\n" + "%s lets you mount a virtual machine filesystem\n" + "Copyright (C) 2009 Red Hat Inc.\n" + "Usage:\n" + " %s [--options] [-- [--FUSE-options]] mountpoint\n" + "Options:\n" + " -a|--add image Add image\n" + " --dir-cache-timeout Set readdir cache timeout (default 5 sec)\n" + " --fuse-help Display extra FUSE options\n" + " --help Display help message and exit\n" + " -m|--mount dev[:mnt] Mount dev on mnt (if omitted, /)\n" + " -n|--no-sync Don't autosync\n" + " -o|--option opt Pass extra option to FUSE\n" + " -r|--ro Mount read-only\n" + " --selinux Enable SELinux support\n" + " --trace Trace guestfs API calls (to stderr)\n" + " -v|--verbose Verbose messages\n" + " -V|--version Display version and exit\n" + ), + program_name, program_name, program_name); + } + exit (status); +} + +int +main (int argc, char *argv[]) +{ + enum { HELP_OPTION = CHAR_MAX + 1 }; + + /* The command line arguments are broadly compatible with (a subset + * of) guestfish. Thus we have to deal mainly with -a, -m and --ro. + */ + static const char *options = "a:m:no:rv?V"; + static const struct option long_options[] = { + { "add", 1, 0, 'a' }, + { "dir-cache-timeout", 1, 0, 0 }, + { "fuse-help", 0, 0, 0 }, + { "help", 0, 0, HELP_OPTION }, + { "mount", 1, 0, 'm' }, + { "no-sync", 0, 0, 'n' }, + { "option", 1, 0, 'o' }, + { "ro", 0, 0, 'r' }, + { "selinux", 0, 0, 0 }, + { "trace", 0, 0, 0 }, + { "verbose", 0, 0, 'v' }, + { "version", 0, 0, 'V' }, + { 0, 0, 0, 0 } + }; + + struct drv *drvs = NULL; + struct drv *drv; + struct mp *mps = NULL; + struct mp *mp; + char *p; + int c, i, r; + int option_index; + struct sigaction sa; + + int fuse_argc = 0; + const char **fuse_argv = 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 (1); \ + } \ + fuse_argv[fuse_argc-1] = (str); \ + fuse_argv[fuse_argc] = NULL; \ + } while (0) + + /* LC_ALL=C is required so we can parse error messages. */ + setenv ("LC_ALL", "C", 1); + + /* Set global program name that is not polluted with libtool artifacts. */ + set_program_name (argv[0]); + + memset (&sa, 0, sizeof sa); + sa.sa_handler = SIG_IGN; + 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 (1); + } + + guestfs_set_autosync (g, 1); + 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"); + + /* If developing, add ./appliance to the path. Note that libtools + * interferes with this because uninstalled guestfish is a shell + * script that runs the real program with an absolute path. Detect + * that too. + * + * BUT if LIBGUESTFS_PATH environment variable is already set by + * the user, then don't override it. + */ + if (getenv ("LIBGUESTFS_PATH") == NULL && + argv[0] && + (argv[0][0] != '/' || strstr (argv[0], "/.libs/lt-") != NULL)) + guestfs_set_path (g, "appliance:" GUESTFS_DEFAULT_PATH); + + for (;;) { + c = getopt_long (argc, argv, options, long_options, &option_index); + if (c == -1) break; + + switch (c) { + case 0: /* options which are long only */ + if (strcmp (long_options[option_index].name, "dir-cache-timeout") == 0) + dir_cache_timeout = atoi (optarg); + else if (strcmp (long_options[option_index].name, "fuse-help") == 0) + fuse_help (); + else if (strcmp (long_options[option_index].name, "selinux") == 0) + guestfs_set_selinux (g, 1); + else if (strcmp (long_options[option_index].name, "trace") == 0) { + ADD_FUSE_ARG ("-f"); + guestfs_set_trace (g, 1); + guestfs_set_recovery_proc (g, 1); + } + else { + fprintf (stderr, _("%s: unknown long option: %s (%d)\n"), + program_name, long_options[option_index].name, option_index); + exit (1); + } + break; + + case 'a': + if (access (optarg, R_OK) != 0) { + perror (optarg); + exit (1); + } + drv = malloc (sizeof (struct drv)); + if (!drv) { + perror ("malloc"); + exit (1); + } + drv->filename = optarg; + drv->next = drvs; + drvs = drv; + break; + + case 'm': + mp = malloc (sizeof (struct mp)); + if (!mp) { + perror ("malloc"); + exit (1); + } + p = strchr (optarg, ':'); + if (p) { + *p = '\0'; + mp->mountpoint = p+1; + } else + mp->mountpoint = bad_cast ("/"); + mp->device = optarg; + mp->next = mps; + mps = mp; + break; + + case 'n': + guestfs_set_autosync (g, 0); + break; + + case 'o': + ADD_FUSE_ARG ("-o"); + ADD_FUSE_ARG (optarg); + break; + + case 'r': + read_only = 1; + break; + + case 'v': + verbose++; + guestfs_set_verbose (g, verbose); + break; + + case 'V': + printf ("%s %s\n", program_name, PACKAGE_VERSION); + exit (0); + + case HELP_OPTION: + usage (0); + + default: + usage (1); + } + } + + /* We must have at least one -a and at least one -m. */ + if (!drvs || !mps) { + fprintf (stderr, + _("%s: must have at least one -a and at least one -m option\n"), + program_name); + exit (1); + } + + /* We'd better have a mountpoint. */ + if (optind+1 != argc) { + fprintf (stderr, + _("%s: you must specify a mountpoint in the host filesystem\n"), + program_name); + exit (1); + } + + /* Do the guest drives and mountpoints. */ + add_drives (drvs); + if (guestfs_launch (g) == -1) + exit (1); + mount_mps (mps); + + /* FUSE example does this, not clear if it's necessary, but ... */ + if (guestfs_umask (g, 0) == -1) + exit (1); + + /* 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). + */ + guestfs_set_error_handler (g, NULL, NULL); + + /* Finish off FUSE args. */ + ADD_FUSE_ARG (argv[optind]); + + 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]); + } + fprintf (stderr, "]\n"); + } + + r = fuse_main (fuse_argc, (char **) fuse_argv, &fg_operations, NULL); + + /* Cleanup. */ + guestfs_close (g); + free_dir_caches (); + + exit (r == -1 ? 1 : 0); +} + +/* List is built in reverse order, so add them in reverse order. */ +static void +add_drives (struct drv *drv) +{ + int r; + + if (drv) { + add_drives (drv->next); + if (!read_only) + r = guestfs_add_drive (g, drv->filename); + else + r = guestfs_add_drive_ro (g, drv->filename); + if (r == -1) + exit (1); + } +} + +/* List is built in reverse order, so mount them in reverse order. */ +static void +mount_mps (struct mp *mp) +{ + int r; + + if (mp) { + mount_mps (mp->next); + if (!read_only) + r = guestfs_mount (g, mp->device, mp->mountpoint); + else + r = guestfs_mount_ro (g, mp->device, mp->mountpoint); + if (r == -1) + exit (1); + } +} diff --git a/fuse/guestmount.pod b/fuse/guestmount.pod new file mode 100644 index 00000000..263959e7 --- /dev/null +++ b/fuse/guestmount.pod @@ -0,0 +1,205 @@ +=encoding utf8 + +=head1 NAME + +guestmount - Mount a guest filesystem on the host using FUSE and libguestfs + +=head1 SYNOPSIS + + guestmount [--options] -a disk.img -m device [--ro] mountpoint + +=head1 WARNING + +You must I<not> use C<guestmount> in read-write mode on live virtual +machines. If you do this, you risk disk corruption in the VM. + +=head1 DESCRIPTION + +The guestmount program can be used to mount virtual machine +filesystems and other disk images on the host. It uses libguestfs for +access to the guest filesystem, and FUSE (the "filesystem in +userspace") to make it appear as a mountable device. + +Along with other options, you have to give at least one device (I<-a> +option) and at least one mountpoint (I<-m> option). How this works is +better explained in the L<guestfish(1)> manual page, or you can use +L<virt-inspector(1)> and/or the the wrapper script +C<guestmount-wrapper> to help you. + +FUSE lets you mount filesystems as non-root. The mountpoint must be +owned by you, and the filesystem will not be visible to any other +users unless you make certain global configuration changes to +C</etc/fuse.conf>. To unmount the filesystem, use the C<fusermount -u> +command. + +=head1 EXAMPLES + +For a typical Windows guest which has its main filesystem on the +first partition: + + guestmount -a windows.img -m /dev/sda1 --ro /mnt + +For a typical Linux guest which has a /boot filesystem on the first +partition, and the root filesystem on a logical volume: + + guestmount -a linux.img -m /dev/VG/LV -m /dev/sda1:/boot --ro /mnt + +To get L<virt-inspector(1)> to do the hard work of detecting guest +mountpoints for you: + + guestmount $(virt-inspector --ro-fish MyGuest) /mnt + +(or use --fish if you don't want it to be a read only mount). The +option is called I<--ro-fish> or I<--fish> because these parameters +are compatible with L<guestfish(1)>. + +If you want to trace the libguestfs calls but without excessive +debugging, we recommend: + + guestmount [-a ... -m ...] --trace /mnt + +If you want to debug the program, we recommend: + + guestmount [-a ... -m ...] --trace --verbose /mnt + +=head1 OPTIONS + +=over 4 + +=item B<-a image> | B<--add image> + +Add a block device or virtual machine image. + +=item B<--dir-cache-timeout N> + +Set the readdir cache timeout to I<N> seconds, the default being 60 +seconds. The readdir cache [actually, there are several +semi-independent caches] is populated after a readdir(2) call with the +stat and extended attributes of the files in the directory, in +anticipation that they will be requested soon after. + +There is also a different attribute cache implemented by FUSE +(see the FUSE option I<-o attr_timeout>), but the FUSE cache +does not anticipate future requests, only cache existing ones. + +=item B<--fuse-help> + +Display help on special FUSE options (see I<-o> below). + +=item B<--help> + +Display brief help and exit. + +=item B<-m dev[:mnt]> | B<--mount dev[:mnt]> + +Mount the named partition or logical volume on the given mountpoint +B<in the guest> (this has nothing to do with mountpoints in the host). + +If the mountpoint is omitted, it defaults to C</>. You have to mount +something on C</>. + +=item B<-n> | B<--no-sync> + +By default, we attempt to sync the guest disk when the FUSE mountpoint +is unmounted. If you specify this option, then we don't attempt to +sync the disk. See the discussion of autosync in the L<guestfs(3)> +manpage. + +=item B<-o option> | B<--option option> + +Pass extra options to FUSE. + +To get a list of all the extra options supported by FUSE, use the +command below. Note that only the FUSE I<-o> options can be passed, +and only some of them are a good idea. + + guestmount --fuse-help + +Some potentially useful FUSE options: + +=over 4 + +=item B<-o allow_other> + +Allow other users to see the filesystem. + +=item B<-o attr_timeout=N> + +Enable attribute caching by FUSE, and set the timeout to I<N> seconds. + +=item B<-o kernel_cache> + +Allow the kernel to cache files (reduces the number of reads +that have to go through the L<guestfs(3)> API). This is generally +a good idea if you can afford the extra memory usage. + +=item B<-o uid=N> B<-o gid=N> + +Use these options to map all UIDs and GIDs inside the guest filesystem +to the chosen values. + +=back + +=item B<-r> | B<--ro> + +Add devices and mount everything read-only. Also disallow writes and +make the disk appear read-only to FUSE. + +This is highly recommended if you are not going to edit the guest +disk. If the guest is running and this option is I<not> supplied, +then there is a strong risk of disk corruption in the guest. We try +to prevent this from happening, but it is not always possible. + +=item B<--selinux> + +Enable SELinux support for the guest. + +=item B<--trace> + +Trace libguestfs calls (to stderr). + +This also stops the daemon from forking into the background. + +=item B<-v> | B<--verbose> + +Enable verbose messages from underlying libguestfs. + +=item B<-V> | B<--version> + +Display the program version and exit. + +=back + +=head1 SEE ALSO + +L<guestfish(1)>, +L<virt-inspector(1)>, +L<virt-cat(1)>, +L<virt-edit(1)>, +L<virt-tar(1)>, +L<guestfs(3)>, +L<http://libguestfs.org/>, +L<http://fuse.sf.net/>. + +=head1 AUTHORS + +Richard W.M. Jones (C<rjones at redhat dot com>) + +=head1 COPYRIGHT + +Copyright (C) 2009 Red Hat Inc. +L<http://libguestfs.org/> + +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. diff --git a/m4/.gitignore b/m4/.gitignore index d5082d73..94898a02 100644 --- a/m4/.gitignore +++ b/m4/.gitignore @@ -123,3 +123,5 @@ xsize.m4 /unistd-safer.m4 /xgetcwd.m4 /xstrndup.m4 +/hash.m4 +/inttostr.m4 diff --git a/po/POTFILES.in b/po/POTFILES.in index 8c3c8f50..d7d12f73 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -71,6 +71,8 @@ fish/rc.c fish/reopen.c fish/tilde.c fish/time.c +fuse/dircache.c +fuse/guestmount.c hivex/hivex.c hivex/hivexget.c hivex/hivexml.c |