diff options
author | Richard W.M. Jones <rjones@redhat.com> | 2011-12-14 08:44:41 +0000 |
---|---|---|
committer | Richard W.M. Jones <rjones@redhat.com> | 2012-03-29 16:45:01 +0100 |
commit | c6f09fac0666260587f95bdfee3c20c9166dae94 (patch) | |
tree | 25b1b6493697034120508518dcfcbc6d34417156 /src/fuse.c | |
parent | 49fdba0ae98b5b6995ccaf0f0ba4f542bfff7ce3 (diff) | |
download | libguestfs-c6f09fac0666260587f95bdfee3c20c9166dae94.tar.gz libguestfs-c6f09fac0666260587f95bdfee3c20c9166dae94.tar.xz libguestfs-c6f09fac0666260587f95bdfee3c20c9166dae94.zip |
New APIs: mount-local, mount-local-run, umount-local (FUSE support in the API).
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.
Note that although mount-local-run is marked as Cancellable, the
current implementation does not support it, but it would be relatively
simple to add it.
Diffstat (limited to 'src/fuse.c')
-rw-r--r-- | src/fuse.c | 1536 |
1 files changed, 1536 insertions, 0 deletions
diff --git a/src/fuse.c b/src/fuse.c new file mode 100644 index 00000000..29232627 --- /dev/null +++ b/src/fuse.c @@ -0,0 +1,1536 @@ +/* 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); + guestfs___free_fuse (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); + guestfs___free_fuse (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); + guestfs___free_fuse (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); + + guestfs___free_fuse (g); + gl_lock_lock (mount_local_lock); + g->localmountpoint = 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; +} + +void +guestfs___free_fuse (guestfs_h *g) +{ + if (g->fuse) + fuse_destroy (g->fuse); /* also closes the channel */ + g->fuse = NULL; + free_dir_caches (g); +} + +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) +{ + if (g->lsc_ht) + hash_free (g->lsc_ht); + if (g->xac_ht) + hash_free (g->xac_ht); + if (g->rlc_ht) + hash_free (g->rlc_ht); + g->lsc_ht = NULL; + g->xac_ht = NULL; + g->rlc_ht = NULL; +} + +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 */ |