Richard W.M. Jones
2012-Mar-29 12:41 UTC
[Libguestfs] [PATCH v3] New APIs: mount-local, mount-local-run and umount-local using FUSE
This changes the proposed API slightly. Previously 'mount-local' generating a 'mounted' event when the filesystem was ready, and from the 'mounted' event you had to effectively do a fork. Now, 'mount-local' just initializes the mountpoint and you have to call 'mount-local-run' to enter the FUSE main loop. Between these calls you can do a fork or whatever other work is needed. Rich.
Richard W.M. Jones
2012-Mar-29 12:41 UTC
[Libguestfs] [PATCH 1/2] New APIs: mount-local, mount-local-run, umount-local (FUSE support in the API).
From: "Richard W.M. Jones" <rjones at 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 at 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 at 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 at 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
Richard W.M. Jones
2012-Mar-29 12:41 UTC
[Libguestfs] [PATCH 2/2] Add test of parallel mount-local calls.
From: "Richard W.M. Jones" <rjones at redhat.com> --- .gitignore | 2 + ocaml/Makefile.am | 25 +++- ocaml/t/exit.c | 44 +++++++ ocaml/t/guestfs_500_parallel_mount_local.ml | 182 +++++++++++++++++++++++++++ po/POTFILES.in | 1 + 5 files changed, 252 insertions(+), 2 deletions(-) create mode 100644 ocaml/t/exit.c create mode 100644 ocaml/t/guestfs_500_parallel_mount_local.ml diff --git a/.gitignore b/.gitignore index 7a000d0..14d5c75 100644 --- a/.gitignore +++ b/.gitignore @@ -236,6 +236,8 @@ ocaml/t/guestfs_400_events.bc ocaml/t/guestfs_400_events.opt ocaml/t/guestfs_400_progress.bc ocaml/t/guestfs_400_progress.opt +ocaml/t/guestfs_500_parallel_mount_local.bc +ocaml/t/guestfs_500_parallel_mount_local.opt *.orig *.patch perl/bindtests.pl diff --git a/ocaml/Makefile.am b/ocaml/Makefile.am index c313532..8c742a2 100644 --- a/ocaml/Makefile.am +++ b/ocaml/Makefile.am @@ -30,6 +30,7 @@ EXTRA_DIST = \ html/.gitignore \ META.in \ run-bindtests \ + t/exit.c \ t/*.ml CLEANFILES = *.cmi *.cmo *.cmx *.cma *.cmxa *.o *.a *.so @@ -87,7 +88,8 @@ if ENABLE_APPLIANCE test_progs += \ t/guestfs_010_basic \ t/guestfs_070_threads \ - t/guestfs_400_progress + t/guestfs_400_progress \ + t/guestfs_500_parallel_mount_local endif TESTS = run-bindtests \ @@ -163,13 +165,32 @@ t/guestfs_400_progress.opt: t/guestfs_400_progress.cmx mlguestfs.cmxa mkdir -p t $(OCAMLFIND) ocamlopt $(OCAMLOPTFLAGS) -cclib -L$(top_builddir)/src/.libs -I . -package unix -linkpkg mlguestfs.cmxa $< -o $@ -# Explicit rules for this test which requires 'threads' package. +t/guestfs_500_parallel_mount_local.bc: t/guestfs_500_parallel_mount_local.cmo mlguestfs.cma libocamltestlib.a + mkdir -p t + LD_LIBRARY_PATH=../src/.libs \ + $(OCAMLFIND) ocamlc -custom $(OCAMLCFLAGS) -I . -package unix,threads -thread -linkpkg mlguestfs.cma libocamltestlib.a $< -o $@ + +t/guestfs_500_parallel_mount_local.opt: t/guestfs_500_parallel_mount_local.cmx mlguestfs.cmxa libocamltestlib.a + mkdir -p t + $(OCAMLFIND) ocamlopt $(OCAMLOPTFLAGS) -cclib -L$(top_builddir)/src/.libs -I . -package unix,threads -thread -linkpkg mlguestfs.cmxa libocamltestlib.a $< -o $@ + +# Explicit rules for these tests which require 'threads' package. t/guestfs_070_threads.cmo: t/guestfs_070_threads.ml mlguestfs.cma $(OCAMLFIND) ocamlc $(OCAMLCFLAGS) -package unix,threads -thread -linkpkg -c $< -o $@ t/guestfs_070_threads.cmx: t/guestfs_070_threads.ml mlguestfs.cmxa $(OCAMLFIND) ocamlopt $(OCAMLOPTFLAGS) -package unix,threads -thread -linkpkg -c $< -o $@ +t/guestfs_500_parallel_mount_local.cmo: t/guestfs_500_parallel_mount_local.ml mlguestfs.cma + $(OCAMLFIND) ocamlc $(OCAMLCFLAGS) -package unix,threads -thread -linkpkg -c $< -o $@ + +t/guestfs_500_parallel_mount_local.cmx: t/guestfs_500_parallel_mount_local.ml mlguestfs.cmxa + $(OCAMLFIND) ocamlopt $(OCAMLOPTFLAGS) -package unix,threads -thread -linkpkg -c $< -o $@ + +noinst_LIBRARIES += libocamltestlib.a +libocamltestlib_a_SOURCES = t/exit.c +libocamltestlib_a_CFLAGS = $(libguestfsocaml_a_CFLAGS) + %.cmi: %.mli $(OCAMLFIND) ocamlc $(OCAMLCFLAGS) -package unix -c $< -o $(builddir)/$@ %.cmo: %.ml mlguestfs.cma diff --git a/ocaml/t/exit.c b/ocaml/t/exit.c new file mode 100644 index 0000000..ca392de --- /dev/null +++ b/ocaml/t/exit.c @@ -0,0 +1,44 @@ +/* libguestfs OCaml bindings + * Copyright (C) 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 + * 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. + */ + +#include <config.h> + +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> + +#include <caml/alloc.h> +#include <caml/fail.h> +#include <caml/memory.h> +#include <caml/misc.h> +#include <caml/mlvalues.h> + +value ocaml_guestfs__exit (value) Noreturn; + +/* _exit : int -> 'a (does not return) */ +value +ocaml_guestfs__exit (value statusv) +{ + CAMLparam1 (statusv); + int status = Int_val (statusv); + + _exit (status); + + /*NOTREACHED*/ + CAMLnoreturn; +} diff --git a/ocaml/t/guestfs_500_parallel_mount_local.ml b/ocaml/t/guestfs_500_parallel_mount_local.ml new file mode 100644 index 0000000..402abc1 --- /dev/null +++ b/ocaml/t/guestfs_500_parallel_mount_local.ml @@ -0,0 +1,182 @@ +(* libguestfs OCaml bindings + * Copyright (C) 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 + * 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. + *) + +(* Test guestfs_mount_local, from a higher level language (it will + * mostly be used first from Python), in parallel threads. OCaml + * allows us to test this at a reasonable speed. + *) + +open Unix +open Printf + +let (//) = Filename.concat + +(* See [exit.c]. *) +external _exit : int -> 'a = "ocaml_guestfs__exit" + +let nr_threads = 2 +let total_time = 60. (* seconds, excluding launch *) +let debug = true (* overview debugging messages *) + +let rec main () + Random.self_init (); + + let threads = ref [] in + for i = 1 to nr_threads do + let filename = sprintf "test%d.img" i in + let mp = sprintf "mp%d" i in + (try rmdir mp with Unix_error _ -> ()); + mkdir mp 0o700; + + if debug then eprintf "%s : starting thread\n%!" mp; + let t = Thread.create start_thread (filename, mp) in + threads := (t, filename, mp) :: !threads + done; + + (* Wait until the threads terminate and delete the files and mountpoints. *) + List.iter ( + fun (t, filename, mp) -> + Thread.join t; + + if debug then eprintf "%s : cleaning up thread\n%!" mp; + unlink filename; + rmdir mp + ) !threads; + + Gc.compact () + +and start_thread (filename, mp) + (* Create a filesystem for the tests. *) + let g = new Guestfs.guestfs () in + + let fd = openfile filename [O_WRONLY;O_CREAT;O_NOCTTY;O_TRUNC] 0o666 in + ftruncate fd (500 * 1024 * 1024); + close fd; + + g#add_drive_opts filename; + g#launch (); + + g#part_disk "/dev/sda" "mbr"; + g#mkfs "ext2" "/dev/sda1"; + g#mount "/dev/sda1" "/"; + + (* Randomly mount the filesystem and repeat. Keep going until we + * finish the test. + *) + let start_t = time () in + let rec loop () + let t = time () in + if t -. start_t < total_time then ( + if debug then eprintf "%s < mounting filesystem\n%!" mp; + g#mount_local mp; + + (* Run test in an exec'd subprocess. *) + let args = [| Sys.executable_name; "--test"; mp |] in + let pid = fork () in + if pid = 0 then ( (* child *) + try execv Sys.executable_name args + with exn -> prerr_endline (Printexc.to_string exn); _exit 1 + ); + + (* Run FUSE main loop. This processes requests until the + * subprocess unmounts the filesystem. + *) + g#mount_local_run (); + + let _, status = waitpid [] pid in + (match status with + | WEXITED 0 -> () + | WEXITED i -> + eprintf "test subprocess failed (exit code %d)\n" i; + exit 1 + | WSIGNALED i | WSTOPPED i -> + eprintf "test subprocess signaled/stopped (signal %d)\n" i; + exit 1 + ); + loop () + ) + in + loop (); + + g#close () + +(* This is run in a child program. *) +and test_mountpoint mp + if debug then eprintf "%s | testing filesystem\n%!" mp; + + (* Run through the same set of tests repeatedly a number of times. + * The aim of this stress test is repeated mount/unmount, not testing + * FUSE itself, so we don't do much here. + *) + for pass = 0 to Random.int 32 do + mkdir (mp // "tmp.d") 0o700; + let chan = open_out (mp // "file") in + let s = String.make (Random.int (128 * 1024)) (Char.chr (Random.int 256)) in + output_string chan s; + close_out chan; + rename (mp // "tmp.d") (mp // "newdir"); + link (mp // "file") (mp // "newfile"); + if Random.int 32 = 0 then sleep 1; + rmdir (mp // "newdir"); + unlink (mp // "file"); + unlink (mp // "newfile") + done; + + if debug then eprintf "%s > unmounting filesystem\n%!" mp; + + unmount mp + +(* We may need to retry this a few times because of processes which + * run in the background jumping into mountpoints. Only display + * errors if it still fails after many retries. + *) +and unmount mp + let logfile = sprintf "%s.fusermount.log" mp in + let unlink_logfile () + try unlink logfile with Unix_error _ -> () + in + unlink_logfile (); + + let run_command () + Sys.command (sprintf "fusermount -u %s >> %s 2>&1" + (Filename.quote mp) (Filename.quote logfile)) = 0 + in + + let rec loop tries + if tries <= 5 then ( + if not (run_command ()) then ( + sleep 1; + loop (tries+1) + ) + ) else ( + ignore (Sys.command (sprintf "cat %s" (Filename.quote logfile))); + eprintf "fusermount: %s: failed, see earlier error messages\n" mp; + exit 1 + ) + in + loop 0; + + unlink_logfile () + +let () + match Array.to_list Sys.argv with + | [ _; "--test"; mp ] -> test_mountpoint mp + | [ _ ] -> main () + | _ -> + eprintf "%s: unknown arguments given to program\n" Sys.executable_name; + exit 1 diff --git a/po/POTFILES.in b/po/POTFILES.in index 7cd55ca..40842ea 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -172,6 +172,7 @@ inspector/virt-inspector.c java/com_redhat_et_libguestfs_GuestFS.c ocaml/guestfs_c.c ocaml/guestfs_c_actions.c +ocaml/t/exit.c perl/Guestfs.c perl/bindtests.pl perl/lib/Sys/Guestfs.pm -- 1.7.9.3
Richard W.M. Jones
2012-Mar-29 20:31 UTC
[Libguestfs] [PATCH v3] New APIs: mount-local, mount-local-run and umount-local using FUSE
On Thu, Mar 29, 2012 at 01:41:42PM +0100, Richard W.M. Jones wrote:> This changes the proposed API slightly. > > Previously 'mount-local' generating a 'mounted' event when the > filesystem was ready, and from the 'mounted' event you had to > effectively do a fork. > > Now, 'mount-local' just initializes the mountpoint and you have to > call 'mount-local-run' to enter the FUSE main loop. Between these > calls you can do a fork or whatever other work is needed.After some discussion and a bit of code review with Matt today, I pushed this. However it really could do with even more testing. Here is an idea: https://rwmj.wordpress.com/2012/03/29/libguestfs-mount-local/ Rich. -- Richard Jones, Virtualization Group, Red Hat http://people.redhat.com/~rjones virt-top is 'top' for virtual machines. Tiny program with many powerful monitoring features, net stats, disk stats, logging, etc. http://et.redhat.com/~rjones/virt-top
Possibly Parallel Threads
- [PATCH v2] New APIs: mount-local and umount-local using FUSE
- [PATCH 0/3] Enable FUSE support in the API via 'mount-local' call.
- [PATCH] fuse: provide a stub "flush" implementation (RHBZ#660687).
- [PATCH] fuse: clear stat structs (RHBZ#660687).
- [PATCH 0/6] Allow non-optargs functions to gain optional arguments.